use indexmap::IndexMap;
use crate::models::{OpenApiSpec, Parameter, PathItem, RequestBody, Response};
pub fn decode_json_pointer_token(token: &str) -> String {
token.replace("~1", "/").replace("~0", "~")
}
pub fn resolve_ref(spec_json: &serde_json::Value, reference: &str) -> Option<serde_json::Value> {
if !reference.starts_with("#/") {
return None; }
let path = &reference[2..];
let components = path.split('/');
let mut current = spec_json;
for component in components {
let unescaped = decode_json_pointer_token(component);
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_json: &serde_json::Value,
parameter: &Parameter,
) -> Option<Parameter> {
if let Some(reference) = ¶meter.reference {
resolve_ref(spec_json, reference).and_then(|resolved| serde_json::from_value(resolved).ok())
} else {
Some(parameter.clone())
}
}
pub fn resolve_response_ref(
spec_json: &serde_json::Value,
response: &Response,
) -> Option<Response> {
if let Some(extensions) = response.extensions.get("$ref")
&& let Some(reference) = extensions.as_str()
&& let Some(resolved) = resolve_ref(spec_json, reference)
{
return serde_json::from_value(resolved).ok();
}
Some(response.clone())
}
pub fn resolve_request_body_ref(
spec_json: &serde_json::Value,
request_body: &RequestBody,
) -> Option<RequestBody> {
if let Some(reference) = &request_body.reference
&& let Some(resolved) = resolve_ref(spec_json, reference)
{
return serde_json::from_value(resolved).ok();
}
Some(request_body.clone())
}
pub fn resolve_path_item_ref(
spec_json: &serde_json::Value,
path_item: &PathItem,
) -> Option<PathItem> {
match &path_item.reference {
None => Some(path_item.clone()),
Some(reference) => resolve_ref(spec_json, reference)
.and_then(|resolved| serde_json::from_value(resolved).ok()),
}
}
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")
&& 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")
&& 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);
}
servers
}
pub fn extract_security_schemes(spec: &OpenApiSpec) -> IndexMap<String, String> {
let mut schemes = IndexMap::new();
if let Some(components) = &spec.components
&& 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")
&& 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 {
let mut result = String::with_capacity(input.len());
let mut last_was_dash = false;
for c in input.to_lowercase().chars() {
if c.is_alphanumeric() || c == '_' {
result.push(c);
last_was_dash = false;
} else if !last_was_dash {
result.push('-');
last_was_dash = true;
}
}
result.trim_matches('-').to_string()
}
pub fn extract_content_type(response: &Response) -> Option<String> {
if let Some(content) = &response.content
&& !content.is_empty()
{
return content.keys().next().map(|s| s.to_string());
}
if response.schema.is_some() {
return Some("application/json".to_string());
}
None
}
#[cfg(test)]
mod tests {
use super::clean_for_id;
#[test]
fn clean_for_id_basic_cases() {
assert_eq!(clean_for_id("Pets_ListPets"), "pets_listpets");
assert_eq!(clean_for_id("GET /pets"), "get-pets");
assert_eq!(clean_for_id("list-pets"), "list-pets");
}
#[test]
fn clean_for_id_collapses_long_dash_runs() {
assert_eq!(clean_for_id("a///b"), "a-b");
assert_eq!(clean_for_id("a / / b"), "a-b");
assert_eq!(clean_for_id("foo----bar"), "foo-bar");
}
#[test]
fn clean_for_id_trims_edge_dashes() {
assert_eq!(clean_for_id("/pets/"), "pets");
assert_eq!(clean_for_id("**bold**"), "bold");
}
}