oag_core/parse/
ref_resolve.rs1use std::collections::HashSet;
2
3use indexmap::IndexMap;
4
5use super::components::Components;
6use super::media_type::MediaType;
7use super::operation::{Operation, PathItem};
8use super::parameter::{Parameter, ParameterOrRef};
9use super::request_body::{RequestBody, RequestBodyOrRef};
10use super::response::{Response, ResponseOrRef};
11use super::schema::{Schema, SchemaOrRef};
12use super::spec::OpenApiSpec;
13use crate::error::ResolveError;
14
15pub struct RefResolver<'a> {
18 components: Option<&'a Components>,
19 visited: HashSet<String>,
20}
21
22impl<'a> RefResolver<'a> {
23 pub fn new(spec: &'a OpenApiSpec) -> Self {
24 Self {
25 components: spec.components.as_ref(),
26 visited: HashSet::new(),
27 }
28 }
29
30 pub fn resolve_spec(&mut self, spec: &OpenApiSpec) -> Result<OpenApiSpec, ResolveError> {
32 let mut resolved = spec.clone();
33
34 for (_path, item) in &mut resolved.paths {
36 self.resolve_path_item(item)?;
37 }
38
39 if let Some(ref mut components) = resolved.components {
41 let schema_names: Vec<String> = components.schemas.keys().cloned().collect();
42 for name in schema_names {
43 let schema = components.schemas.get(&name).unwrap().clone();
44 let resolved_schema = self.resolve_schema_or_ref(&schema)?;
45 components.schemas.insert(name, resolved_schema);
46 }
47 }
48
49 Ok(resolved)
50 }
51
52 fn resolve_path_item(&mut self, item: &mut PathItem) -> Result<(), ResolveError> {
53 let mut resolved_params = Vec::new();
55 for p in &item.parameters {
56 resolved_params.push(self.resolve_parameter_or_ref(p)?);
57 }
58 item.parameters = resolved_params;
59
60 macro_rules! resolve_op {
62 ($op:expr) => {
63 if let Some(ref mut op) = $op {
64 self.resolve_operation(op)?;
65 }
66 };
67 }
68 resolve_op!(item.get);
69 resolve_op!(item.post);
70 resolve_op!(item.put);
71 resolve_op!(item.delete);
72 resolve_op!(item.patch);
73 resolve_op!(item.options);
74 resolve_op!(item.head);
75 resolve_op!(item.trace);
76 Ok(())
77 }
78
79 fn resolve_operation(&mut self, op: &mut Operation) -> Result<(), ResolveError> {
80 let mut resolved_params = Vec::new();
82 for p in &op.parameters {
83 resolved_params.push(self.resolve_parameter_or_ref(p)?);
84 }
85 op.parameters = resolved_params;
86
87 if let Some(ref body) = op.request_body {
89 let resolved = self.resolve_request_body_or_ref(body)?;
90 op.request_body = Some(resolved);
91 }
92
93 let mut resolved_responses = IndexMap::new();
95 for (status, resp) in &op.responses {
96 resolved_responses.insert(status.clone(), self.resolve_response_or_ref(resp)?);
97 }
98 op.responses = resolved_responses;
99
100 Ok(())
101 }
102
103 pub fn resolve_schema_or_ref(
104 &mut self,
105 schema_or_ref: &SchemaOrRef,
106 ) -> Result<SchemaOrRef, ResolveError> {
107 match schema_or_ref {
108 SchemaOrRef::Ref { ref_path } => {
109 if self.visited.contains(ref_path) {
110 return Ok(schema_or_ref.clone());
113 }
114 self.visited.insert(ref_path.clone());
115 let resolved = self.lookup_schema(ref_path)?;
116 let result =
117 self.resolve_schema_or_ref(&SchemaOrRef::Schema(Box::new(resolved)))?;
118 self.visited.remove(ref_path);
119 Ok(result)
120 }
121 SchemaOrRef::Schema(schema) => {
122 let resolved = self.resolve_schema(schema)?;
123 Ok(SchemaOrRef::Schema(Box::new(resolved)))
124 }
125 }
126 }
127
128 fn resolve_schema(&mut self, schema: &Schema) -> Result<Schema, ResolveError> {
129 let mut resolved = schema.clone();
130
131 let mut resolved_props = IndexMap::new();
133 for (name, prop) in &schema.properties {
134 resolved_props.insert(name.clone(), self.resolve_schema_or_ref(prop)?);
135 }
136 resolved.properties = resolved_props;
137
138 if let Some(ref items) = schema.items {
140 resolved.items = Some(Box::new(self.resolve_schema_or_ref(items)?));
141 }
142
143 resolved.all_of = schema
145 .all_of
146 .iter()
147 .map(|s| self.resolve_schema_or_ref(s))
148 .collect::<Result<Vec<_>, _>>()?;
149 resolved.one_of = schema
150 .one_of
151 .iter()
152 .map(|s| self.resolve_schema_or_ref(s))
153 .collect::<Result<Vec<_>, _>>()?;
154 resolved.any_of = schema
155 .any_of
156 .iter()
157 .map(|s| self.resolve_schema_or_ref(s))
158 .collect::<Result<Vec<_>, _>>()?;
159
160 if let Some(super::schema::AdditionalProperties::Schema(ref s)) =
162 schema.additional_properties
163 {
164 resolved.additional_properties = Some(super::schema::AdditionalProperties::Schema(
165 Box::new(self.resolve_schema_or_ref(s)?),
166 ));
167 }
168
169 Ok(resolved)
170 }
171
172 fn resolve_parameter_or_ref(
173 &mut self,
174 param: &ParameterOrRef,
175 ) -> Result<ParameterOrRef, ResolveError> {
176 match param {
177 ParameterOrRef::Ref { ref_path } => {
178 let resolved = self.lookup_parameter(ref_path)?;
179 Ok(ParameterOrRef::Parameter(resolved))
180 }
181 ParameterOrRef::Parameter(p) => {
182 let mut resolved = p.clone();
183 if let Some(ref s) = p.schema {
184 resolved.schema = Some(self.resolve_schema_or_ref(s)?);
185 }
186 Ok(ParameterOrRef::Parameter(resolved))
187 }
188 }
189 }
190
191 fn resolve_request_body_or_ref(
192 &mut self,
193 body: &RequestBodyOrRef,
194 ) -> Result<RequestBodyOrRef, ResolveError> {
195 match body {
196 RequestBodyOrRef::Ref { ref_path } => {
197 let resolved = self.lookup_request_body(ref_path)?;
198 let mut rb = resolved;
199 self.resolve_media_types(&mut rb.content)?;
200 Ok(RequestBodyOrRef::RequestBody(rb))
201 }
202 RequestBodyOrRef::RequestBody(rb) => {
203 let mut resolved = rb.clone();
204 self.resolve_media_types(&mut resolved.content)?;
205 Ok(RequestBodyOrRef::RequestBody(resolved))
206 }
207 }
208 }
209
210 fn resolve_response_or_ref(
211 &mut self,
212 resp: &ResponseOrRef,
213 ) -> Result<ResponseOrRef, ResolveError> {
214 match resp {
215 ResponseOrRef::Ref { ref_path } => {
216 let resolved = self.lookup_response(ref_path)?;
217 let mut r = resolved;
218 self.resolve_media_types(&mut r.content)?;
219 Ok(ResponseOrRef::Response(r))
220 }
221 ResponseOrRef::Response(r) => {
222 let mut resolved = r.clone();
223 self.resolve_media_types(&mut resolved.content)?;
224 Ok(ResponseOrRef::Response(resolved))
225 }
226 }
227 }
228
229 fn resolve_media_types(
230 &mut self,
231 content: &mut IndexMap<String, MediaType>,
232 ) -> Result<(), ResolveError> {
233 let keys: Vec<String> = content.keys().cloned().collect();
234 for key in keys {
235 let mt = content.get(&key).unwrap().clone();
236 let mut resolved_mt = mt;
237 if let Some(ref s) = resolved_mt.schema {
238 resolved_mt.schema = Some(self.resolve_schema_or_ref(s)?);
239 }
240 if let Some(ref s) = resolved_mt.item_schema {
241 resolved_mt.item_schema = Some(self.resolve_schema_or_ref(s)?);
242 }
243 content.insert(key, resolved_mt);
244 }
245 Ok(())
246 }
247
248 fn lookup_schema(&self, ref_path: &str) -> Result<Schema, ResolveError> {
251 let name = parse_ref_name(ref_path, "schemas")?;
252 self.components
253 .and_then(|c| c.schemas.get(name))
254 .and_then(|s| match s {
255 SchemaOrRef::Schema(schema) => Some(schema.as_ref().clone()),
256 SchemaOrRef::Ref { ref_path: inner } => {
257 let inner_name = parse_ref_name(inner, "schemas").ok()?;
259 self.components
260 .and_then(|c| c.schemas.get(inner_name))
261 .and_then(|s2| match s2 {
262 SchemaOrRef::Schema(schema) => Some(schema.as_ref().clone()),
263 _ => None,
264 })
265 }
266 })
267 .ok_or_else(|| ResolveError::RefTargetNotFound(ref_path.to_string()))
268 }
269
270 fn lookup_parameter(&self, ref_path: &str) -> Result<Parameter, ResolveError> {
271 let name = parse_ref_name(ref_path, "parameters")?;
272 self.components
273 .and_then(|c| c.parameters.get(name))
274 .and_then(|p| match p {
275 ParameterOrRef::Parameter(param) => Some(param.clone()),
276 _ => None,
277 })
278 .ok_or_else(|| ResolveError::RefTargetNotFound(ref_path.to_string()))
279 }
280
281 fn lookup_request_body(&self, ref_path: &str) -> Result<RequestBody, ResolveError> {
282 let name = parse_ref_name(ref_path, "requestBodies")?;
283 self.components
284 .and_then(|c| c.request_bodies.get(name))
285 .and_then(|rb| match rb {
286 RequestBodyOrRef::RequestBody(body) => Some(body.clone()),
287 _ => None,
288 })
289 .ok_or_else(|| ResolveError::RefTargetNotFound(ref_path.to_string()))
290 }
291
292 fn lookup_response(&self, ref_path: &str) -> Result<Response, ResolveError> {
293 let name = parse_ref_name(ref_path, "responses")?;
294 self.components
295 .and_then(|c| c.responses.get(name))
296 .and_then(|r| match r {
297 ResponseOrRef::Response(resp) => Some(resp.clone()),
298 _ => None,
299 })
300 .ok_or_else(|| ResolveError::RefTargetNotFound(ref_path.to_string()))
301 }
302}
303
304fn parse_ref_name<'a>(ref_path: &'a str, expected_section: &str) -> Result<&'a str, ResolveError> {
306 let stripped = ref_path
307 .strip_prefix("#/components/")
308 .ok_or_else(|| ResolveError::InvalidRefFormat(ref_path.to_string()))?;
309 let (section, name) = stripped
310 .split_once('/')
311 .ok_or_else(|| ResolveError::InvalidRefFormat(ref_path.to_string()))?;
312 if section != expected_section {
313 return Err(ResolveError::InvalidRefFormat(format!(
314 "expected section '{}', got '{}' in {}",
315 expected_section, section, ref_path
316 )));
317 }
318 Ok(name)
319}