use crate::constants;
pub mod parser;
pub mod transformer;
pub mod validator;
pub use parser::parse_openapi;
pub use transformer::SpecTransformer;
pub use validator::SpecValidator;
use crate::error::Error;
use openapiv3::{OpenAPI, Operation, Parameter, PathItem, ReferenceOr};
use std::collections::HashSet;
pub type HttpMethodsIter<'a> = [(&'static str, &'a Option<Operation>); 8];
#[must_use]
pub const fn http_methods_iter(item: &PathItem) -> HttpMethodsIter<'_> {
[
(constants::HTTP_METHOD_GET, &item.get),
(constants::HTTP_METHOD_POST, &item.post),
(constants::HTTP_METHOD_PUT, &item.put),
(constants::HTTP_METHOD_DELETE, &item.delete),
(constants::HTTP_METHOD_PATCH, &item.patch),
(constants::HTTP_METHOD_HEAD, &item.head),
(constants::HTTP_METHOD_OPTIONS, &item.options),
("TRACE", &item.trace),
]
}
pub const MAX_REFERENCE_DEPTH: usize = 10;
pub fn resolve_parameter_reference(spec: &OpenAPI, reference: &str) -> Result<Parameter, Error> {
let mut visited = HashSet::new();
resolve_parameter_reference_with_visited(spec, reference, &mut visited, 0)
}
pub fn resolve_schema_reference(
spec: &OpenAPI,
reference: &str,
) -> Result<openapiv3::Schema, Error> {
let mut visited = HashSet::new();
resolve_schema_reference_with_visited(spec, reference, &mut visited, 0)
}
fn resolve_schema_reference_with_visited(
spec: &OpenAPI,
reference: &str,
visited: &mut HashSet<String>,
depth: usize,
) -> Result<openapiv3::Schema, Error> {
if depth >= MAX_REFERENCE_DEPTH {
return Err(Error::validation_error(format!(
"Maximum reference depth ({MAX_REFERENCE_DEPTH}) exceeded while resolving '{reference}'"
)));
}
if !visited.insert(reference.to_string()) {
return Err(Error::validation_error(format!(
"Circular reference detected: '{reference}' is part of a reference cycle"
)));
}
if !reference.starts_with("#/components/schemas/") {
return Err(Error::validation_error(format!(
"Invalid schema reference format: '{reference}'. Expected format: #/components/schemas/{{name}}"
)));
}
let schema_name = reference
.strip_prefix("#/components/schemas/")
.ok_or_else(|| {
Error::validation_error(format!("Invalid schema reference: '{reference}'"))
})?;
let components = spec.components.as_ref().ok_or_else(|| {
Error::validation_error(
"Cannot resolve schema reference: OpenAPI spec has no components section".to_string(),
)
})?;
let schema_ref = components.schemas.get(schema_name).ok_or_else(|| {
Error::validation_error(format!("Schema '{schema_name}' not found in components"))
})?;
match schema_ref {
ReferenceOr::Item(schema) => Ok(schema.clone()),
ReferenceOr::Reference {
reference: nested_ref,
} => resolve_schema_reference_with_visited(spec, nested_ref, visited, depth + 1),
}
}
fn resolve_parameter_reference_with_visited(
spec: &OpenAPI,
reference: &str,
visited: &mut HashSet<String>,
depth: usize,
) -> Result<Parameter, Error> {
if depth >= MAX_REFERENCE_DEPTH {
return Err(Error::validation_error(format!(
"Maximum reference depth ({MAX_REFERENCE_DEPTH}) exceeded while resolving '{reference}'"
)));
}
if !visited.insert(reference.to_string()) {
return Err(Error::validation_error(format!(
"Circular reference detected: '{reference}' is part of a reference cycle"
)));
}
if !reference.starts_with("#/components/parameters/") {
return Err(Error::validation_error(format!(
"Invalid parameter reference format: '{reference}'. Expected format: #/components/parameters/{{name}}"
)));
}
let param_name = reference
.strip_prefix("#/components/parameters/")
.ok_or_else(|| {
Error::validation_error(format!("Invalid parameter reference: '{reference}'"))
})?;
let components = spec.components.as_ref().ok_or_else(|| {
Error::validation_error(
"Cannot resolve parameter reference: OpenAPI spec has no components section"
.to_string(),
)
})?;
let param_ref = components.parameters.get(param_name).ok_or_else(|| {
Error::validation_error(format!("Parameter '{param_name}' not found in components"))
})?;
match param_ref {
ReferenceOr::Item(param) => Ok(param.clone()),
ReferenceOr::Reference {
reference: nested_ref,
} => resolve_parameter_reference_with_visited(spec, nested_ref, visited, depth + 1),
}
}