paperclip_core/v2/
resolver.rs

1use super::{
2    models::{
3        Either, HttpMethod, Reference, Resolvable, ResolvableParameter, ResolvablePathItem,
4        ResolvableResponse,
5    },
6    Schema,
7};
8use crate::error::ValidationError;
9use heck::ToPascalCase;
10
11use std::{cell::RefCell, collections::BTreeMap, mem};
12
13// FIXME: The resolver is not in its best. It "just" works atm.
14
15const DEF_REF_PREFIX: &str = "#/definitions/";
16const PARAM_REF_PREFIX: &str = "#/parameters/";
17const RESP_REF_PREFIX: &str = "#/responses/";
18
19type DefinitionsMap<S> = BTreeMap<String, Resolvable<S>>;
20type OperationsMap<S> = BTreeMap<String, ResolvablePathItem<S>>;
21type ParametersMap<S> = BTreeMap<String, ResolvableParameter<S>>;
22type ResponsesMap<S> = BTreeMap<String, ResolvableResponse<S>>;
23
24/// API schema resolver. This visits each definition and resolves
25/// `$ref` field (if any) by finding the associated definition and
26/// replacing the field with a reference to the actual definition.
27// FIXME: Move all validation to resolver.
28pub(crate) struct Resolver<S> {
29    /// List of definitions that must be marked as cyclic while resolving a definition.
30    cyclic_defs: RefCell<Vec<Resolvable<S>>>,
31    /// Globally defined object definitions.
32    pub defs: DefinitionsMap<S>,
33    /// Paths and the corresponding operations.
34    pub paths: OperationsMap<S>,
35    /// Globally defined parameters.
36    pub params: ParametersMap<S>,
37    /// Globally defined responses;
38    pub resp: ResponsesMap<S>,
39}
40
41impl<S>
42    From<(
43        DefinitionsMap<S>,
44        OperationsMap<S>,
45        ParametersMap<S>,
46        ResponsesMap<S>,
47    )> for Resolver<S>
48{
49    fn from(
50        (defs, paths, params, resp): (
51            DefinitionsMap<S>,
52            OperationsMap<S>,
53            ParametersMap<S>,
54            ResponsesMap<S>,
55        ),
56    ) -> Self {
57        Resolver {
58            cyclic_defs: vec![].into(),
59            defs,
60            paths,
61            params,
62            resp,
63        }
64    }
65}
66
67impl<S> Resolver<S>
68where
69    S: Schema + Default,
70{
71    /// Visit definitions and resolve them!
72    pub fn resolve(&mut self) -> Result<(), ValidationError> {
73        // Resolve path operations first. We may encounter anonymous
74        // definitions along the way, which we'll insert into `self.defs`
75        // and we'll have to resolve them anyway.
76        let mut paths = mem::take(&mut self.paths);
77        paths.iter_mut().try_for_each(|(path, map)| {
78            log::trace!("Checking path: {}", path);
79            self.resolve_operations(path, map)
80        })?;
81        self.paths = paths;
82
83        // Set the names of all schemas.
84        for (name, schema) in &self.defs {
85            schema.write().unwrap().set_name(name);
86        }
87
88        for (name, schema) in &self.defs {
89            log::trace!("Entering: {}", name);
90            self.resolve_definitions_no_root_ref(schema)?;
91
92            for def in self.cyclic_defs.borrow_mut().drain(..) {
93                log::debug!(
94                    "Cyclic definition detected: {:?}",
95                    def.read().unwrap().name().unwrap()
96                );
97                def.write().unwrap().set_cyclic(true);
98            }
99        }
100
101        Ok(())
102    }
103
104    /// We've passed some definition. Resolve it assuming that it doesn't
105    /// contain any reference.
106    fn resolve_definitions_no_root_ref(
107        &self,
108        schema: &Resolvable<S>,
109    ) -> Result<(), ValidationError> {
110        let mut schema = match schema.try_write().ok() {
111            Some(s) => s,
112            None => {
113                self.cyclic_defs.borrow_mut().push(schema.clone());
114                return Ok(());
115            }
116        };
117
118        if let Some(inner) = schema.items_mut() {
119            return self.resolve_definitions(inner);
120        }
121
122        if let Some(props) = schema.properties_mut() {
123            props.iter_mut().try_for_each(|(k, s)| {
124                log::trace!("Resolving property {:?}", k);
125                self.resolve_definitions(s)
126            })?;
127        }
128
129        if let Some(props) = schema
130            .additional_properties_mut()
131            .and_then(|s| s.right_mut())
132        {
133            self.resolve_definitions(props)?;
134        }
135
136        Ok(())
137    }
138
139    /// Resolve the given definition. If it contains a reference, find and assign it,
140    /// otherwise traverse further.
141    fn resolve_definitions(&self, schema: &mut Resolvable<S>) -> Result<(), ValidationError> {
142        let ref_def = {
143            let s = match schema.try_read().ok() {
144                Some(s) => s,
145                None => {
146                    self.cyclic_defs.borrow_mut().push(schema.clone());
147                    return Ok(());
148                }
149            };
150
151            if let Some(ref_name) = s.reference() {
152                log::trace!("Resolving definition {}", ref_name);
153                Some(self.resolve_definition_reference(ref_name)?)
154            } else {
155                None
156            }
157        };
158
159        if let Some(new) = ref_def {
160            *schema = match schema {
161                Resolvable::Raw(old) => Resolvable::Resolved {
162                    old: old.clone(),
163                    new: (*new).clone(),
164                },
165                _ => unimplemented!("schema already resolved?"),
166            };
167        }
168
169        self.resolve_definitions_no_root_ref(&*schema)
170    }
171
172    /// Resolve a given operation.
173    fn resolve_operations(
174        &mut self,
175        path: &str,
176        map: &mut ResolvablePathItem<S>,
177    ) -> Result<(), ValidationError> {
178        for (&method, op) in &mut map.methods {
179            self.resolve_parameters(Some(method), path, &mut op.parameters)?;
180            for resp in op.responses.values_mut() {
181                let ref_resp = if let Some(r) = resp.left() {
182                    log::trace!("Resolving response {}", r.reference);
183                    Some(self.resolve_response_reference(&r.reference)?)
184                } else {
185                    None
186                };
187
188                if let Some(new) = ref_resp {
189                    *resp = Either::Right(new);
190                }
191
192                let mut response = resp.write().unwrap();
193                self.resolve_operation_schema(
194                    &mut response.schema,
195                    Some(method),
196                    path,
197                    "Response",
198                )?;
199            }
200        }
201
202        self.resolve_parameters(None, path, &mut map.parameters)
203    }
204
205    /// Resolve the given bunch of parameters.
206    fn resolve_parameters(
207        &mut self,
208        method: Option<HttpMethod>,
209        path: &str,
210        params: &mut [Either<Reference, ResolvableParameter<S>>],
211    ) -> Result<(), ValidationError> {
212        for p in params.iter_mut() {
213            let ref_param = if let Some(r) = p.left() {
214                log::trace!("Resolving parameter {}", r.reference);
215                Some(self.resolve_parameter_reference(&r.reference)?)
216            } else {
217                None
218            };
219
220            if let Some(new) = ref_param {
221                *p = Either::Right(new);
222            }
223
224            let mut param = p.write().unwrap();
225            self.resolve_operation_schema(&mut param.schema, method, path, "Body")?;
226        }
227
228        Ok(())
229    }
230
231    /// Resolves request/response schema in operation.
232    fn resolve_operation_schema(
233        &mut self,
234        s: &mut Option<Resolvable<S>>,
235        method: Option<HttpMethod>,
236        path: &str,
237        suffix: &str,
238    ) -> Result<(), ValidationError> {
239        let schema = match s.as_mut() {
240            Some(s) => s,
241            _ => return Ok(()),
242        };
243
244        match schema {
245            Resolvable::Raw(ref s) if s.read().unwrap().reference().is_none() => {
246                // We've encountered an anonymous schema definition in some
247                // parameter/response. Give it a name and add it to global definitions.
248                let prefix = method.map(|s| s.to_string()).unwrap_or_default();
249                let def_name = (prefix + path + suffix).to_pascal_case();
250                let mut ref_schema = S::default();
251                ref_schema.set_reference(format!("{}{}", DEF_REF_PREFIX, def_name));
252                let old_schema = mem::replace(schema, ref_schema.into());
253                self.defs.insert(def_name, old_schema);
254            }
255            _ => (),
256        }
257
258        self.resolve_definitions(schema)?;
259        Ok(())
260    }
261
262    /// Given a name (from `$ref` field), get a reference to the definition.
263    fn resolve_definition_reference(&self, name: &str) -> Result<Resolvable<S>, ValidationError> {
264        if !name.starts_with(DEF_REF_PREFIX) {
265            return Err(ValidationError::InvalidRefUri(name.into()));
266        }
267
268        let name = &name[DEF_REF_PREFIX.len()..];
269        let schema = self
270            .defs
271            .get(name)
272            .ok_or_else(|| ValidationError::MissingReference(name.into()))?;
273        Ok(schema.clone())
274    }
275
276    /// Given a name (from `$ref` field), get a reference to the parameter.
277    fn resolve_parameter_reference(
278        &self,
279        name: &str,
280    ) -> Result<ResolvableParameter<S>, ValidationError> {
281        if !name.starts_with(PARAM_REF_PREFIX) {
282            return Err(ValidationError::InvalidRefUri(name.into()));
283        }
284
285        let name = &name[PARAM_REF_PREFIX.len()..];
286        let param = self
287            .params
288            .get(name)
289            .ok_or_else(|| ValidationError::MissingReference(name.into()))?;
290        Ok(param.clone())
291    }
292
293    /// Given a name (from `$ref` field), get a reference to the response.
294    fn resolve_response_reference(
295        &self,
296        name: &str,
297    ) -> Result<ResolvableResponse<S>, ValidationError> {
298        if !name.starts_with(RESP_REF_PREFIX) {
299            return Err(ValidationError::InvalidRefUri(name.into()));
300        }
301
302        let name = &name[RESP_REF_PREFIX.len()..];
303        let resp = self
304            .resp
305            .get(name)
306            .ok_or_else(|| ValidationError::MissingReference(name.into()))?;
307        Ok(resp.clone())
308    }
309}