use std::collections::HashMap;
use crate::models::{OpenApiSpec, Parameter, Response};
pub fn resolve_ref(spec: &OpenApiSpec, reference: &str) -> Option<serde_json::Value> {
if !reference.starts_with("#/") {
return None; }
let path = &reference[2..];
let components = path.split('/');
let spec_json = serde_json::to_value(spec).ok()?;
let mut current = &spec_json;
for component in components {
let unescaped = component.replace("~1", "/").replace("~0", "~");
if let Some(obj) = current.as_object() {
if let Some(value) = obj.get(&unescaped) {
current = value;
} else {
return None; }
} else if let Some(arr) = current.as_array() {
if let Ok(index) = unescaped.parse::<usize>() {
if index < arr.len() {
current = &arr[index];
} else {
return None; }
} else {
return None; }
} else {
return None; }
}
Some(current.clone())
}
pub fn resolve_parameter_ref(spec: &OpenApiSpec, parameter: &Parameter) -> Option<Parameter> {
if let Some(extensions) = parameter.extensions.get("$ref") {
if let Some(reference) = extensions.as_str() {
if let Some(resolved) = resolve_ref(spec, reference) {
return serde_json::from_value(resolved).ok();
}
}
}
Some(parameter.clone())
}
pub fn resolve_response_ref(spec: &OpenApiSpec, response: &Response) -> Option<Response> {
if let Some(extensions) = response.extensions.get("$ref") {
if let Some(reference) = extensions.as_str() {
if let Some(resolved) = resolve_ref(spec, reference) {
return serde_json::from_value(resolved).ok();
}
}
}
Some(response.clone())
}
pub fn extract_servers(spec: &OpenApiSpec) -> Vec<String> {
let mut servers = Vec::new();
if let Some(server_list) = &spec.servers {
for server in server_list {
servers.push(server.url.clone());
}
}
else if let Some(host) = spec.extensions.get("host") {
if let Some(host_str) = host.as_str() {
let mut base_url = if host_str.starts_with("http") {
host_str.to_string()
} else {
format!("https://{}", host_str)
};
if let Some(base_path) = spec.extensions.get("basePath") {
if let Some(path_str) = base_path.as_str() {
if !base_url.ends_with('/') && !path_str.starts_with('/') {
base_url.push('/');
}
base_url.push_str(path_str);
}
}
servers.push(base_url);
}
}
if servers.is_empty() {
servers.push("https://api.example.com".to_string());
}
servers
}
pub fn extract_security_schemes(spec: &OpenApiSpec) -> HashMap<String, String> {
let mut schemes = HashMap::new();
if let Some(components) = &spec.components {
if let Some(security_schemes) = &components.security_schemes {
for (name, scheme) in security_schemes {
let desc = format!(
"{} ({})",
scheme.description.as_deref().unwrap_or(""),
scheme.security_type
);
schemes.insert(name.clone(), desc);
}
}
}
if let Some(security_defs) = spec.extensions.get("securityDefinitions") {
if let Some(defs_map) = security_defs.as_object() {
for (name, def) in defs_map {
if let Some(def_obj) = def.as_object() {
let type_str = def_obj
.get("type")
.and_then(|t| t.as_str())
.unwrap_or("unknown");
let desc = def_obj
.get("description")
.and_then(|d| d.as_str())
.unwrap_or("");
schemes.insert(name.clone(), format!("{} ({})", desc, type_str));
}
}
}
}
schemes
}
pub fn clean_for_id(input: &str) -> String {
input
.to_lowercase()
.replace(|c: char| !c.is_alphanumeric() && c != '-' && c != '_', "-")
.replace("--", "-")
.trim_matches('-')
.to_string()
}
pub fn extract_content_type(response: &Response) -> Option<String> {
if let Some(content) = &response.content {
if !content.is_empty() {
return content.keys().next().map(|s| s.to_string());
}
}
if response.schema.is_some() {
return Some("application/json".to_string());
}
None
}