use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; use serde::Serialize;use serde_json::{Map, Value, json};use sha2::{Digest, Sha256}; #[cfg(target_arch = "wasm32")]use wasm_bindgen::prelude::*; const OPEN_API_HTTP_METHODS: [&str; 8] = [ "get", "put", "post", "delete", "patch", "head", "options", "trace",]; const OPEN_API_PARAMETER_LOCATIONS: [&str; 4] = ["path", "query", "header", "cookie"]; #[derive(Serialize, Clone)]struct OpenApiToolParameter { name: String, location: String, required: bool,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct OpenApiToolRequestBody { required: bool, content_types: Vec<String>,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct OpenApiInvocationPayload { method: String, path_template: String, parameters: Vec<OpenApiToolParameter>, request_body: Option<OpenApiToolRequestBody>,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct DiscoveryTypingPayload { #[serde(skip_serializing_if = "Option::is_none")] input_schema: Option<Value>, #[serde(skip_serializing_if = "Option::is_none")] output_schema: Option<Value>, #[serde(skip_serializing_if = "Option::is_none")] ref_hint_keys: Option<Vec<String>>,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct OpenApiExample { value_json: String, #[serde(skip_serializing_if = "Option::is_none")] media_type: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] label: Option<String>,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct OpenApiParameterDocumentation { name: String, location: String, required: bool, #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] examples: Option<Vec<OpenApiExample>>,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct OpenApiRequestBodyDocumentation { #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] examples: Option<Vec<OpenApiExample>>,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct OpenApiResponseDocumentation { status_code: String, #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, content_types: Vec<String>, #[serde(skip_serializing_if = "Option::is_none")] examples: Option<Vec<OpenApiExample>>,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct OpenApiToolDocumentation { #[serde(skip_serializing_if = "Option::is_none")] summary: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] deprecated: Option<bool>, parameters: Vec<OpenApiParameterDocumentation>, #[serde(skip_serializing_if = "Option::is_none")] request_body: Option<OpenApiRequestBodyDocumentation>, #[serde(skip_serializing_if = "Option::is_none")] response: Option<OpenApiResponseDocumentation>,} #[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]struct OpenApiExtractedTool { tool_id: String, #[serde(skip_serializing_if = "Option::is_none")] operation_id: Option<String>, tags: Vec<String>, name: String, description: Option<String>, method: String, path: String, invocation: OpenApiInvocationPayload, operation_hash: String, #[serde(skip_serializing_if = "Option::is_none")] typing: Option<DiscoveryTypingPayload>, #[serde(skip_serializing_if = "Option::is_none")] documentation: Option<OpenApiToolDocumentation>,} #[derive(Serialize)]#[serde(rename_all = "camelCase")]struct OpenApiToolManifest { version: u8, source_hash: String, tools: Vec<OpenApiExtractedTool>, #[serde(skip_serializing_if = "Option::is_none")] ref_hint_table: Option<BTreeMap<String, String>>,} pub fn extract_manifest_json( source_name: &str, openapi_document_text: &str, pretty: bool,) -> Result<String, String> { let spec = parse_openapi_document(openapi_document_text)?; let manifest = extract_openapi_manifest(source_name, &spec)?; if pretty { serde_json::to_string_pretty(&manifest).map_err(|error| format!("serialize manifest: {error}")) } else { serde_json::to_string(&manifest).map_err(|error| format!("serialize manifest: {error}")) }} #[cfg(target_arch = "wasm32")]#[wasm_bindgen]pub fn extract_manifest_json_wasm( source_name: &str, openapi_document_text: &str,) -> Result<String, JsValue> { extract_manifest_json(source_name, openapi_document_text, false) .map_err(|error| JsValue::from_str(&error))} fn parse_openapi_document(input: &str) -> Result<Value, String> { let text = input.trim(); if text.is_empty() { return Err("OpenAPI document is empty".to_string()); } match serde_json::from_str::<Value>(text) { Ok(value) => Ok(value), Err(_) => serde_yaml::from_str::<Value>(text) .map_err(|error| format!("Unable to parse OpenAPI document as JSON or YAML: {error}")), }} fn as_object(value: &Value) -> Option<&Map<String, Value>> { value.as_object()} fn as_trimmed_string(value: Option<&Value>) -> Option<String> { let candidate = value?.as_str()?.trim(); if candidate.is_empty() { return None; } Some(candidate.to_string())} fn response_status_rank(status_code: &str) -> i32 { if status_code.len() == 3 && status_code.starts_with('2') && status_code.chars().all(|character| character.is_ascii_digit()) { return 0; } if status_code == "default" { return 1; } 2} fn to_stable_value(value: &Value) -> Value { match value { Value::Array(items) => Value::Array(items.iter().map(to_stable_value).collect()), Value::Object(object) => { let mut keys: Vec<&String> = object.keys().collect(); keys.sort(); let mut stable = Map::new(); for key in keys { if let Some(item) = object.get(key) { stable.insert(key.clone(), to_stable_value(item)); } } Value::Object(stable) } Value::Number(number) => { if let Some(float_value) = number.as_f64() && float_value.is_finite() && float_value.fract() == 0.0 { if float_value >= 0.0 && float_value <= u64::MAX as f64 { let unsigned = float_value as u64; if unsigned as f64 == float_value { return Value::Number(serde_json::Number::from(unsigned)); } } if float_value >= i64::MIN as f64 && float_value <= i64::MAX as f64 { let signed = float_value as i64; if signed as f64 == float_value { return Value::Number(serde_json::Number::from(signed)); } } } Value::Number(number.clone()) } _ => value.clone(), }} fn hash_unknown(value: &Value) -> Result<String, String> { let stable_json = serde_json::to_string(&to_stable_value(value)).map_err(|error| format!("hash json: {error}"))?; let mut hasher = Sha256::new(); hasher.update(stable_json.as_bytes()); Ok(format!("{:x}", hasher.finalize()))} fn encode_stable_json(value: &Value) -> Result<String, String> { serde_json::to_string(&to_stable_value(value)).map_err(|error| format!("encode stable json: {error}"))} fn to_extracted_tool_parameter_from_record(record: &Map<String, Value>) -> Option<OpenApiToolParameter> { let name = record.get("name")?.as_str()?.trim(); if name.is_empty() { return None; } let location = record.get("in")?.as_str()?; if !OPEN_API_PARAMETER_LOCATIONS.contains(&location) { return None; } let required = location == "path" || record.get("required").and_then(Value::as_bool) == Some(true); Some(OpenApiToolParameter { name: name.to_string(), location: location.to_string(), required, })} fn to_extracted_tool_parameter(root: &Value, value: &Value) -> Option<OpenApiToolParameter> { let record = resolve_local_reference_object(root, value)?; to_extracted_tool_parameter_from_record(&record)} fn merge_parameters(root: &Value, path_item: &Map<String, Value>, operation: &Map<String, Value>) -> Vec<OpenApiToolParameter> { let mut merged: Vec<OpenApiToolParameter> = collect_parameter_record_by_key(root, path_item, operation) .into_values() .filter_map(|record| to_extracted_tool_parameter_from_record(&record)) .collect(); merged.sort_by(|left, right| { left.location .cmp(&right.location) .then_with(|| left.name.cmp(&right.name)) }); merged} fn extract_request_body(root: &Value, operation: &Map<String, Value>) -> Option<OpenApiToolRequestBody> { let request_body = resolve_request_body_record(root, operation)?; let content = request_body.get("content").and_then(as_object); Some(OpenApiToolRequestBody { required: request_body.get("required").and_then(Value::as_bool) == Some(true), content_types: collect_content_types(content), })} fn build_invocation_metadata( root: &Value, method: &str, path_value: &str, path_item: &Map<String, Value>, operation: &Map<String, Value>,) -> OpenApiInvocationPayload { OpenApiInvocationPayload { method: method.to_string(), path_template: path_value.to_string(), parameters: merge_parameters(root, path_item, operation), request_body: extract_request_body(root, operation), }} fn collect_ref_keys(value: &Value, refs: &mut BTreeSet<String>) { match value { Value::Array(items) => { for item in items { collect_ref_keys(item, refs); } } Value::Object(record) => { if let Some(reference) = record.get("$ref").and_then(Value::as_str) && reference.starts_with("#/") { refs.insert(reference.to_string()); } for nested in record.values() { collect_ref_keys(nested, refs); } } _ => {} }} fn resolve_json_pointer<'a>(root: &'a Value, pointer: &str) -> Option<&'a Value> { if !pointer.starts_with("#/") { return None; } let mut current = root; for part in pointer[2..].split('/') { let decoded = part.replace("~1", "/").replace("~0", "~"); let next = current.as_object()?.get(&decoded)?; current = next; } Some(current)} fn resolve_local_reference_value( root: &Value, value: &Value, active_refs: &HashSet<String>,) -> Value { let Some(record) = as_object(value) else { return value.clone(); }; let Some(reference) = record.get("$ref").and_then(Value::as_str) else { return value.clone(); }; if !reference.starts_with("#/") || active_refs.contains(reference) { return value.clone(); } let Some(resolved) = resolve_json_pointer(root, reference) else { return value.clone(); }; let mut next_active_refs = active_refs.clone(); next_active_refs.insert(reference.to_string()); let mut resolved_value = resolve_local_reference_value(root, resolved, &next_active_refs); if let Some(resolved_record) = resolved_value.as_object_mut() { for (key, nested) in record { if key != "$ref" { resolved_record.insert(key.clone(), nested.clone()); } } return resolved_value; } value.clone()} fn resolve_local_reference_object(root: &Value, value: &Value) -> Option<Map<String, Value>> { as_object(&resolve_local_reference_value(root, value, &HashSet::new())).cloned()} fn collect_parameter_record_by_key( root: &Value, path_item: &Map<String, Value>, operation: &Map<String, Value>,) -> HashMap<String, Map<String, Value>> { let mut records = HashMap::new(); let mut add_parameters = |candidate: Option<&Value>| { let Some(items) = candidate.and_then(Value::as_array) else { return; }; for item in items { let Some(record) = resolve_local_reference_object(root, item) else { continue; }; let Some(parameter) = to_extracted_tool_parameter_from_record(&record) else { continue; }; records.insert( format!("{}:{}", parameter.location, parameter.name), record, ); } }; add_parameters(path_item.get("parameters")); add_parameters(operation.get("parameters")); records} fn resolve_request_body_record(root: &Value, operation: &Map<String, Value>) -> Option<Map<String, Value>> { resolve_local_reference_object(root, operation.get("requestBody")?)} fn collect_content_types(content: Option<&Map<String, Value>>) -> Vec<String> { let mut content_types = content .map(|object| object.keys().cloned().collect::<Vec<String>>()) .unwrap_or_default(); content_types.sort(); content_types} fn pick_content_entry(content: Option<&Value>) -> Option<(String, Map<String, Value>)> { let content_record = as_object(content?)?; let mut keys: Vec<String> = content_record.keys().cloned().collect(); keys.sort(); let mut preferred = Vec::with_capacity(keys.len() + 1); preferred.push("application/json".to_string()); preferred.extend(keys); let mut seen = HashSet::new(); for media_type in preferred { if !seen.insert(media_type.clone()) { continue; } let Some(media_type_value) = content_record.get(&media_type).and_then(as_object) else { continue; }; return Some((media_type, media_type_value.clone())); } None} fn pick_example_values(value: &Value, label: Option<String>, media_type: Option<String>) -> Result<Vec<OpenApiExample>, String> { let mut examples = Vec::new(); let Some(record) = as_object(value) else { return Ok(examples); }; if let Some(example_value) = record.get("example") { examples.push(OpenApiExample { value_json: encode_stable_json(example_value)?, media_type: media_type.clone(), label: label.clone(), }); } if let Some(example_values) = record.get("examples").and_then(Value::as_object) { let mut keys: Vec<String> = example_values.keys().cloned().collect(); keys.sort(); for key in keys { let Some(example_entry) = example_values.get(&key) else { continue; }; let example_record = as_object(example_entry)Lines 1–500 of 1016. Load more
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; use serde::Serialize; use serde_json::{Map, Value, json}; use sha2::{Digest, Sha256}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; const OPEN_API_HTTP_METHODS: [&str; 8] = [ "get", "put", "post", "delete", "patch", "head", "options", "trace", ]; const OPEN_API_PARAMETER_LOCATIONS: [&str; 4] = ["path", "query", "header", "cookie"]; #[derive(Serialize, Clone)] struct OpenApiToolParameter { name: String, location: String, required: bool, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct OpenApiToolRequestBody { required: bool, content_types: Vec<String>, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct OpenApiInvocationPayload { method: String, path_template: String, parameters: Vec<OpenApiToolParameter>, request_body: Option<OpenApiToolRequestBody>, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct DiscoveryTypingPayload { #[serde(skip_serializing_if = "Option::is_none")] input_schema: Option<Value>, #[serde(skip_serializing_if = "Option::is_none")] output_schema: Option<Value>, #[serde(skip_serializing_if = "Option::is_none")] ref_hint_keys: Option<Vec<String>>, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct OpenApiExample { value_json: String, #[serde(skip_serializing_if = "Option::is_none")] media_type: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] label: Option<String>, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct OpenApiParameterDocumentation { name: String, location: String, required: bool, #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] examples: Option<Vec<OpenApiExample>>, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct OpenApiRequestBodyDocumentation { #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] examples: Option<Vec<OpenApiExample>>, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct OpenApiResponseDocumentation { status_code: String, #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, content_types: Vec<String>, #[serde(skip_serializing_if = "Option::is_none")] examples: Option<Vec<OpenApiExample>>, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct OpenApiToolDocumentation { #[serde(skip_serializing_if = "Option::is_none")] summary: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] deprecated: Option<bool>, parameters: Vec<OpenApiParameterDocumentation>, #[serde(skip_serializing_if = "Option::is_none")] request_body: Option<OpenApiRequestBodyDocumentation>, #[serde(skip_serializing_if = "Option::is_none")] response: Option<OpenApiResponseDocumentation>, } #[derive(Serialize, Clone)] #[serde(rename_all = "camelCase")] struct OpenApiExtractedTool { tool_id: String, #[serde(skip_serializing_if = "Option::is_none")] operation_id: Option<String>, tags: Vec<String>, name: String, description: Option<String>, method: String, path: String, invocation: OpenApiInvocationPayload, operation_hash: String, #[serde(skip_serializing_if = "Option::is_none")] typing: Option<DiscoveryTypingPayload>, #[serde(skip_serializing_if = "Option::is_none")] documentation: Option<OpenApiToolDocumentation>, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct OpenApiToolManifest { version: u8, source_hash: String, tools: Vec<OpenApiExtractedTool>, #[serde(skip_serializing_if = "Option::is_none")] ref_hint_table: Option<BTreeMap<String, String>>, } pub fn extract_manifest_json( source_name: &str, openapi_document_text: &str, pretty: bool, ) -> Result<String, String> { let spec = parse_openapi_document(openapi_document_text)?; let manifest = extract_openapi_manifest(source_name, &spec)?; if pretty { serde_json::to_string_pretty(&manifest).map_err(|error| format!("serialize manifest: {error}")) } else { serde_json::to_string(&manifest).map_err(|error| format!("serialize manifest: {error}")) } } #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub fn extract_manifest_json_wasm( source_name: &str, openapi_document_text: &str, ) -> Result<String, JsValue> { extract_manifest_json(source_name, openapi_document_text, false) .map_err(|error| JsValue::from_str(&error)) } fn parse_openapi_document(input: &str) -> Result<Value, String> { let text = input.trim(); if text.is_empty() { return Err("OpenAPI document is empty".to_string()); } match serde_json::from_str::<Value>(text) { Ok(value) => Ok(value), Err(_) => serde_yaml::from_str::<Value>(text) .map_err(|error| format!("Unable to parse OpenAPI document as JSON or YAML: {error}")), } } fn as_object(value: &Value) -> Option<&Map<String, Value>> { value.as_object() } fn as_trimmed_string(value: Option<&Value>) -> Option<String> { let candidate = value?.as_str()?.trim(); if candidate.is_empty() { return None; } Some(candidate.to_string()) } fn response_status_rank(status_code: &str) -> i32 { if status_code.len() == 3 && status_code.starts_with('2') && status_code.chars().all(|character| character.is_ascii_digit()) { return 0; } if status_code == "default" { return 1; } 2 } fn to_stable_value(value: &Value) -> Value { match value { Value::Array(items) => Value::Array(items.iter().map(to_stable_value).collect()), Value::Object(object) => { let mut keys: Vec<&String> = object.keys().collect(); keys.sort(); let mut stable = Map::new(); for key in keys { if let Some(item) = object.get(key) { stable.insert(key.clone(), to_stable_value(item)); } } Value::Object(stable) } Value::Number(number) => { if let Some(float_value) = number.as_f64() && float_value.is_finite() && float_value.fract() == 0.0 { if float_value >= 0.0 && float_value <= u64::MAX as f64 { let unsigned = float_value as u64; if unsigned as f64 == float_value { return Value::Number(serde_json::Number::from(unsigned)); } } if float_value >= i64::MIN as f64 && float_value <= i64::MAX as f64 { let signed = float_value as i64; if signed as f64 == float_value { return Value::Number(serde_json::Number::from(signed)); } } } Value::Number(number.clone()) } _ => value.clone(), } } fn hash_unknown(value: &Value) -> Result<String, String> { let stable_json = serde_json::to_string(&to_stable_value(value)).map_err(|error| format!("hash json: {error}"))?; let mut hasher = Sha256::new(); hasher.update(stable_json.as_bytes()); Ok(format!("{:x}", hasher.finalize())) } fn encode_stable_json(value: &Value) -> Result<String, String> { serde_json::to_string(&to_stable_value(value)).map_err(|error| format!("encode stable json: {error}")) } fn to_extracted_tool_parameter_from_record(record: &Map<String, Value>) -> Option<OpenApiToolParameter> { let name = record.get("name")?.as_str()?.trim(); if name.is_empty() { return None; } let location = record.get("in")?.as_str()?; if !OPEN_API_PARAMETER_LOCATIONS.contains(&location) { return None; } let required = location == "path" || record.get("required").and_then(Value::as_bool) == Some(true); Some(OpenApiToolParameter { name: name.to_string(), location: location.to_string(), required, }) } fn to_extracted_tool_parameter(root: &Value, value: &Value) -> Option<OpenApiToolParameter> { let record = resolve_local_reference_object(root, value)?; to_extracted_tool_parameter_from_record(&record) } fn merge_parameters(root: &Value, path_item: &Map<String, Value>, operation: &Map<String, Value>) -> Vec<OpenApiToolParameter> { let mut merged: Vec<OpenApiToolParameter> = collect_parameter_record_by_key(root, path_item, operation) .into_values() .filter_map(|record| to_extracted_tool_parameter_from_record(&record)) .collect(); merged.sort_by(|left, right| { left.location .cmp(&right.location) .then_with(|| left.name.cmp(&right.name)) }); merged } fn extract_request_body(root: &Value, operation: &Map<String, Value>) -> Option<OpenApiToolRequestBody> { let request_body = resolve_request_body_record(root, operation)?; let content = request_body.get("content").and_then(as_object); Some(OpenApiToolRequestBody { required: request_body.get("required").and_then(Value::as_bool) == Some(true), content_types: collect_content_types(content), }) } fn build_invocation_metadata( root: &Value, method: &str, path_value: &str, path_item: &Map<String, Value>, operation: &Map<String, Value>, ) -> OpenApiInvocationPayload { OpenApiInvocationPayload { method: method.to_string(), path_template: path_value.to_string(), parameters: merge_parameters(root, path_item, operation), request_body: extract_request_body(root, operation), } } fn collect_ref_keys(value: &Value, refs: &mut BTreeSet<String>) { match value { Value::Array(items) => { for item in items { collect_ref_keys(item, refs); } } Value::Object(record) => { if let Some(reference) = record.get("$ref").and_then(Value::as_str) && reference.starts_with("#/") { refs.insert(reference.to_string()); } for nested in record.values() { collect_ref_keys(nested, refs); } } _ => {} } } fn resolve_json_pointer<'a>(root: &'a Value, pointer: &str) -> Option<&'a Value> { if !pointer.starts_with("#/") { return None; } let mut current = root; for part in pointer[2..].split('/') { let decoded = part.replace("~1", "/").replace("~0", "~"); let next = current.as_object()?.get(&decoded)?; current = next; } Some(current) } fn resolve_local_reference_value( root: &Value, value: &Value, active_refs: &HashSet<String>, ) -> Value { let Some(record) = as_object(value) else { return value.clone(); }; let Some(reference) = record.get("$ref").and_then(Value::as_str) else { return value.clone(); }; if !reference.starts_with("#/") || active_refs.contains(reference) { return value.clone(); } let Some(resolved) = resolve_json_pointer(root, reference) else { return value.clone(); }; let mut next_active_refs = active_refs.clone(); next_active_refs.insert(reference.to_string()); let mut resolved_value = resolve_local_reference_value(root, resolved, &next_active_refs); if let Some(resolved_record) = resolved_value.as_object_mut() { for (key, nested) in record { if key != "$ref" { resolved_record.insert(key.clone(), nested.clone()); } } return resolved_value; } value.clone() } fn resolve_local_reference_object(root: &Value, value: &Value) -> Option<Map<String, Value>> { as_object(&resolve_local_reference_value(root, value, &HashSet::new())).cloned() } fn collect_parameter_record_by_key( root: &Value, path_item: &Map<String, Value>, operation: &Map<String, Value>, ) -> HashMap<String, Map<String, Value>> { let mut records = HashMap::new(); let mut add_parameters = |candidate: Option<&Value>| { let Some(items) = candidate.and_then(Value::as_array) else { return; }; for item in items { let Some(record) = resolve_local_reference_object(root, item) else { continue; }; let Some(parameter) = to_extracted_tool_parameter_from_record(&record) else { continue; }; records.insert( format!("{}:{}", parameter.location, parameter.name), record, ); } }; add_parameters(path_item.get("parameters")); add_parameters(operation.get("parameters")); records } fn resolve_request_body_record(root: &Value, operation: &Map<String, Value>) -> Option<Map<String, Value>> { resolve_local_reference_object(root, operation.get("requestBody")?) } fn collect_content_types(content: Option<&Map<String, Value>>) -> Vec<String> { let mut content_types = content .map(|object| object.keys().cloned().collect::<Vec<String>>()) .unwrap_or_default(); content_types.sort(); content_types } fn pick_content_entry(content: Option<&Value>) -> Option<(String, Map<String, Value>)> { let content_record = as_object(content?)?; let mut keys: Vec<String> = content_record.keys().cloned().collect(); keys.sort(); let mut preferred = Vec::with_capacity(keys.len() + 1); preferred.push("application/json".to_string()); preferred.extend(keys); let mut seen = HashSet::new(); for media_type in preferred { if !seen.insert(media_type.clone()) { continue; } let Some(media_type_value) = content_record.get(&media_type).and_then(as_object) else { continue; }; return Some((media_type, media_type_value.clone())); } None } fn pick_example_values(value: &Value, label: Option<String>, media_type: Option<String>) -> Result<Vec<OpenApiExample>, String> { let mut examples = Vec::new(); let Some(record) = as_object(value) else { return Ok(examples); }; if let Some(example_value) = record.get("example") { examples.push(OpenApiExample { value_json: encode_stable_json(example_value)?, media_type: media_type.clone(), label: label.clone(), }); } if let Some(example_values) = record.get("examples").and_then(Value::as_object) { let mut keys: Vec<String> = example_values.keys().cloned().collect(); keys.sort(); for key in keys { let Some(example_entry) = example_values.get(&key) else { continue; }; let example_record = as_object(example_entry)