Skip to main content

openapiv3_resolve/resolved/
document.rs

1use super::resolver::{Resolvable, Resolver};
2use super::{ResolvedComponents, ResolvedOperation, ResolvedParameter, Shared};
3use crate::ResolveError;
4use indexmap::IndexMap;
5use openapiv3::{
6    Callback, ExternalDocumentation, Info, OpenAPI, Operation, PathItem, Paths,
7    SecurityRequirement, Server, Tag,
8};
9
10/// [`OpenAPI`] with every `$ref` in the document replaced by the item it named.
11///
12/// Built with [`TryFrom<&OpenAPI>`](#impl-TryFrom<%26OpenAPI>-for-ResolvedOpenAPI).
13/// Every reference to the same component resolves to the same [`Shared`]
14/// allocation, so [`Shared::as_ptr`] tells whether two sites named the same
15/// component, and the entries of [`components`](Self::components) are those
16/// very allocations. The document is only ever borrowed from, never taken
17/// apart; see [`Shared`] for why.
18#[derive(Debug, PartialEq)]
19pub struct ResolvedOpenAPI {
20    openapi: String,
21    info: Info,
22    servers: Vec<Server>,
23    paths: ResolvedPaths,
24    components: Option<ResolvedComponents>,
25    security: Option<Vec<SecurityRequirement>>,
26    tags: Vec<Tag>,
27    external_docs: Option<ExternalDocumentation>,
28    extensions: IndexMap<String, serde_json::Value>,
29}
30
31// The fields are private so that no part of the document can be moved out
32// of it: every recursive schema edge relies on its target staying in
33// `components` for as long as anything borrowed from the document exists.
34impl ResolvedOpenAPI {
35    /// See [`OpenAPI::openapi`].
36    pub fn openapi(&self) -> &str {
37        &self.openapi
38    }
39
40    /// See [`OpenAPI::info`].
41    pub fn info(&self) -> &Info {
42        &self.info
43    }
44
45    /// See [`OpenAPI::servers`].
46    pub fn servers(&self) -> &[Server] {
47        &self.servers
48    }
49
50    /// See [`OpenAPI::paths`].
51    pub fn paths(&self) -> &ResolvedPaths {
52        &self.paths
53    }
54
55    /// See [`OpenAPI::components`].
56    pub fn components(&self) -> Option<&ResolvedComponents> {
57        self.components.as_ref()
58    }
59
60    /// See [`OpenAPI::security`].
61    pub fn security(&self) -> Option<&[SecurityRequirement]> {
62        self.security.as_deref()
63    }
64
65    /// See [`OpenAPI::tags`].
66    pub fn tags(&self) -> &[Tag] {
67        &self.tags
68    }
69
70    /// See [`OpenAPI::external_docs`].
71    pub fn external_docs(&self) -> Option<&ExternalDocumentation> {
72        self.external_docs.as_ref()
73    }
74
75    /// See [`OpenAPI::extensions`].
76    pub fn extensions(&self) -> &IndexMap<String, serde_json::Value> {
77        &self.extensions
78    }
79}
80
81/// [`Paths`] with every `$ref` replaced by the path item it named.
82#[derive(Debug, PartialEq)]
83pub struct ResolvedPaths {
84    /// See [`Paths::paths`].
85    pub paths: IndexMap<String, Shared<ResolvedPathItem>>,
86    /// See [`Paths::extensions`].
87    pub extensions: IndexMap<String, serde_json::Value>,
88}
89
90/// [`PathItem`] with every `$ref` replaced by the item it named.
91#[derive(Debug, PartialEq)]
92pub struct ResolvedPathItem {
93    /// See [`PathItem::summary`].
94    pub summary: Option<String>,
95    /// See [`PathItem::description`].
96    pub description: Option<String>,
97    /// See [`PathItem::get`].
98    pub get: Option<ResolvedOperation>,
99    /// See [`PathItem::put`].
100    pub put: Option<ResolvedOperation>,
101    /// See [`PathItem::post`].
102    pub post: Option<ResolvedOperation>,
103    /// See [`PathItem::delete`].
104    pub delete: Option<ResolvedOperation>,
105    /// See [`PathItem::options`].
106    pub options: Option<ResolvedOperation>,
107    /// See [`PathItem::head`].
108    pub head: Option<ResolvedOperation>,
109    /// See [`PathItem::patch`].
110    pub patch: Option<ResolvedOperation>,
111    /// See [`PathItem::trace`].
112    pub trace: Option<ResolvedOperation>,
113    /// See [`PathItem::servers`].
114    pub servers: Vec<Server>,
115    /// See [`PathItem::parameters`].
116    pub parameters: Vec<Shared<ResolvedParameter>>,
117    /// See [`PathItem::extensions`].
118    pub extensions: IndexMap<String, serde_json::Value>,
119}
120
121impl ResolvedPathItem {
122    /// The operations this path item defines, keyed by lower-case HTTP method.
123    pub fn iter(&self) -> impl Iterator<Item = (&'static str, &ResolvedOperation)> {
124        [
125            ("get", &self.get),
126            ("put", &self.put),
127            ("post", &self.post),
128            ("delete", &self.delete),
129            ("options", &self.options),
130            ("head", &self.head),
131            ("patch", &self.patch),
132            ("trace", &self.trace),
133        ]
134        .into_iter()
135        .filter_map(|(method, operation)| operation.as_ref().map(|operation| (method, operation)))
136    }
137}
138
139/// [`Callback`] with every `$ref` replaced by the item it named.
140pub type ResolvedCallback = IndexMap<String, ResolvedPathItem>;
141
142impl Resolvable for OpenAPI {
143    type Resolved = ResolvedOpenAPI;
144
145    fn resolve_inline(&self, cx: &mut Resolver<'_>) -> Result<Self::Resolved, ResolveError> {
146        Ok(ResolvedOpenAPI {
147            openapi: self.openapi.clone(),
148            info: self.info.clone(),
149            servers: self.servers.clone(),
150            // Components first, so the paths find every component already
151            // resolved; the result is the same either way.
152            components: self
153                .components
154                .as_ref()
155                .map(|components| components.resolve_inline(cx))
156                .transpose()?,
157            paths: self.paths.resolve_inline(cx)?,
158            security: self.security.clone(),
159            tags: self.tags.clone(),
160            external_docs: self.external_docs.clone(),
161            extensions: self.extensions.clone(),
162        })
163    }
164}
165
166impl Resolvable for Paths {
167    type Resolved = ResolvedPaths;
168
169    fn resolve_inline(&self, cx: &mut Resolver<'_>) -> Result<Self::Resolved, ResolveError> {
170        Ok(ResolvedPaths {
171            paths: cx.section(&self.paths)?,
172            extensions: self.extensions.clone(),
173        })
174    }
175}
176
177impl Resolvable for PathItem {
178    type Resolved = ResolvedPathItem;
179
180    fn resolve_inline(&self, cx: &mut Resolver<'_>) -> Result<Self::Resolved, ResolveError> {
181        let mut operation = |operation: &Option<Operation>| {
182            operation
183                .as_ref()
184                .map(|operation| operation.resolve_inline(cx))
185                .transpose()
186        };
187        let get = operation(&self.get)?;
188        let put = operation(&self.put)?;
189        let post = operation(&self.post)?;
190        let delete = operation(&self.delete)?;
191        let options = operation(&self.options)?;
192        let head = operation(&self.head)?;
193        let patch = operation(&self.patch)?;
194        let trace = operation(&self.trace)?;
195        Ok(ResolvedPathItem {
196            summary: self.summary.clone(),
197            description: self.description.clone(),
198            get,
199            put,
200            post,
201            delete,
202            options,
203            head,
204            patch,
205            trace,
206            servers: self.servers.clone(),
207            parameters: cx.ref_or_vec(&self.parameters)?,
208            extensions: self.extensions.clone(),
209        })
210    }
211}
212
213impl Resolvable for Callback {
214    type Resolved = ResolvedCallback;
215
216    fn resolve_inline(&self, cx: &mut Resolver<'_>) -> Result<Self::Resolved, ResolveError> {
217        super::resolve_map(self, cx)
218    }
219}