1use crate::constants;
7
8pub mod parser;
9pub mod transformer;
10pub mod validator;
11
12pub use parser::parse_openapi;
13pub use transformer::SpecTransformer;
14pub use validator::SpecValidator;
15
16use crate::error::Error;
17use openapiv3::{OpenAPI, Operation, Parameter, PathItem, ReferenceOr};
18use std::collections::HashSet;
19
20pub type HttpMethodsIter<'a> = [(&'static str, &'a Option<Operation>); 8];
22
23#[must_use]
31pub const fn http_methods_iter(item: &PathItem) -> HttpMethodsIter<'_> {
32 [
33 (constants::HTTP_METHOD_GET, &item.get),
34 (constants::HTTP_METHOD_POST, &item.post),
35 (constants::HTTP_METHOD_PUT, &item.put),
36 (constants::HTTP_METHOD_DELETE, &item.delete),
37 (constants::HTTP_METHOD_PATCH, &item.patch),
38 (constants::HTTP_METHOD_HEAD, &item.head),
39 (constants::HTTP_METHOD_OPTIONS, &item.options),
40 ("TRACE", &item.trace),
41 ]
42}
43
44pub const MAX_REFERENCE_DEPTH: usize = 10;
46
47pub fn resolve_parameter_reference(spec: &OpenAPI, reference: &str) -> Result<Parameter, Error> {
64 let mut visited = HashSet::new();
65 resolve_parameter_reference_with_visited(spec, reference, &mut visited, 0)
66}
67
68pub fn resolve_schema_reference(
110 spec: &OpenAPI,
111 reference: &str,
112) -> Result<openapiv3::Schema, Error> {
113 let mut visited = HashSet::new();
114 resolve_schema_reference_with_visited(spec, reference, &mut visited, 0)
115}
116
117fn resolve_schema_reference_with_visited(
119 spec: &OpenAPI,
120 reference: &str,
121 visited: &mut HashSet<String>,
122 depth: usize,
123) -> Result<openapiv3::Schema, Error> {
124 if depth >= MAX_REFERENCE_DEPTH {
126 return Err(Error::validation_error(format!(
127 "Maximum reference depth ({MAX_REFERENCE_DEPTH}) exceeded while resolving '{reference}'"
128 )));
129 }
130
131 if !visited.insert(reference.to_string()) {
133 return Err(Error::validation_error(format!(
134 "Circular reference detected: '{reference}' is part of a reference cycle"
135 )));
136 }
137
138 if !reference.starts_with("#/components/schemas/") {
141 return Err(Error::validation_error(format!(
142 "Invalid schema reference format: '{reference}'. Expected format: #/components/schemas/{{name}}"
143 )));
144 }
145
146 let schema_name = reference
147 .strip_prefix("#/components/schemas/")
148 .ok_or_else(|| {
149 Error::validation_error(format!("Invalid schema reference: '{reference}'"))
150 })?;
151
152 let components = spec.components.as_ref().ok_or_else(|| {
154 Error::validation_error(
155 "Cannot resolve schema reference: OpenAPI spec has no components section".to_string(),
156 )
157 })?;
158
159 let schema_ref = components.schemas.get(schema_name).ok_or_else(|| {
160 Error::validation_error(format!("Schema '{schema_name}' not found in components"))
161 })?;
162
163 match schema_ref {
165 ReferenceOr::Item(schema) => Ok(schema.clone()),
166 ReferenceOr::Reference {
167 reference: nested_ref,
168 } => resolve_schema_reference_with_visited(spec, nested_ref, visited, depth + 1),
169 }
170}
171
172fn resolve_parameter_reference_with_visited(
174 spec: &OpenAPI,
175 reference: &str,
176 visited: &mut HashSet<String>,
177 depth: usize,
178) -> Result<Parameter, Error> {
179 if depth >= MAX_REFERENCE_DEPTH {
181 return Err(Error::validation_error(format!(
182 "Maximum reference depth ({MAX_REFERENCE_DEPTH}) exceeded while resolving '{reference}'"
183 )));
184 }
185
186 if !visited.insert(reference.to_string()) {
188 return Err(Error::validation_error(format!(
189 "Circular reference detected: '{reference}' is part of a reference cycle"
190 )));
191 }
192
193 if !reference.starts_with("#/components/parameters/") {
196 return Err(Error::validation_error(format!(
197 "Invalid parameter reference format: '{reference}'. Expected format: #/components/parameters/{{name}}"
198 )));
199 }
200
201 let param_name = reference
202 .strip_prefix("#/components/parameters/")
203 .ok_or_else(|| {
204 Error::validation_error(format!("Invalid parameter reference: '{reference}'"))
205 })?;
206
207 let components = spec.components.as_ref().ok_or_else(|| {
209 Error::validation_error(
210 "Cannot resolve parameter reference: OpenAPI spec has no components section"
211 .to_string(),
212 )
213 })?;
214
215 let param_ref = components.parameters.get(param_name).ok_or_else(|| {
216 Error::validation_error(format!("Parameter '{param_name}' not found in components"))
217 })?;
218
219 match param_ref {
221 ReferenceOr::Item(param) => Ok(param.clone()),
222 ReferenceOr::Reference {
223 reference: nested_ref,
224 } => resolve_parameter_reference_with_visited(spec, nested_ref, visited, depth + 1),
225 }
226}