openapiv3/
openapi.rs

1use crate::*;
2use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
6pub struct OpenAPI {
7    /// REQUIRED. This string MUST be the semantic version number of the
8    /// OpenAPI Specification version that the OpenAPI document uses.
9    /// The openapi field SHOULD be used by tooling specifications and
10    /// clients to interpret the OpenAPI document. This is not related to
11    /// the API info.version string.
12    pub openapi: String,
13    /// REQUIRED. Provides metadata about the API.
14    /// The metadata MAY be used by tooling as required.
15    pub info: Info,
16    /// An array of Server Objects, which provide connectivity information to a
17    /// target server. If the servers property is not provided, or is an empty
18    /// array, the default value would be a Server Object with a url value of /.
19    #[serde(default, skip_serializing_if = "Vec::is_empty")]
20    pub servers: Vec<Server>,
21    /// REQUIRED. The available paths and operations for the API.
22    pub paths: Paths,
23    /// An element to hold various schemas for the specification.
24    #[serde(default, skip_serializing_if = "Components::is_empty")]
25    pub components: Components,
26    /// A declaration of which security mechanisms can be used across the API.
27    /// The list of values includes alternative security requirement objects
28    /// that can be used. Only one of the security requirement objects need to
29    /// be satisfied to authorize a request. Individual operations can override
30    /// this definition. Global security settings may be overridden on a per-path
31    /// basis.
32    #[serde(default, skip_serializing_if = "Vec::is_empty")]
33    pub security: Vec<SecurityRequirement>,
34    /// A list of tags used by the specification with additional metadata.
35    /// The order of the tags can be used to reflect on their order by the
36    /// parsing tools. Not all tags that are used by the Operation Object
37    /// must be declared. The tags that are not declared MAY be organized
38    /// randomly or based on the tool's logic. Each tag name in the list
39    /// MUST be unique.
40    #[serde(default, skip_serializing_if = "Vec::is_empty")]
41    pub tags: Vec<Tag>,
42    /// Additional external documentation.
43    #[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")]
44    pub external_docs: Option<ExternalDocumentation>,
45    /// Inline extensions to this object.
46    #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
47    pub extensions: IndexMap<String, serde_json::Value>,
48}
49
50impl std::ops::Deref for OpenAPI {
51    type Target = Components;
52
53    fn deref(&self) -> &Self::Target {
54        &self.components
55    }
56}
57
58impl std::ops::DerefMut for OpenAPI {
59    fn deref_mut(&mut self) -> &mut Self::Target {
60        &mut self.components
61    }
62}
63
64impl OpenAPI {
65    /// Iterates through all [Operation]s in this API.
66    ///
67    /// The iterated items are tuples of `(&str, &str, &Operation, &PathItem)` containing
68    /// the path, method, and the operation.
69    ///
70    /// Path items containing `$ref`s are skipped.
71    pub fn operations(&self) -> impl Iterator<Item=(&str, &str, &Operation, &PathItem)> {
72        self.paths
73            .iter()
74            .filter_map(|(path, item)| item.as_item().map(|i| (path, i)))
75            .flat_map(|(path, item)| {
76                item.iter()
77                    .map(move |(method, op)| (path.as_str(), method, op, item))
78            })
79    }
80
81    pub fn operations_mut(&mut self) -> impl Iterator<Item=(&str, &str, &mut Operation)> {
82        self.paths
83            .iter_mut()
84            .filter_map(|(path, item)| item.as_mut().map(|i| (path, i)))
85            .flat_map(|(path, item)| {
86                item.iter_mut()
87                    .map(move |(method, op)| (path.as_str(), method, op))
88            })
89    }
90
91    pub fn get_operation_mut(&mut self, operation_id: &str) -> Option<&mut Operation> {
92        self.operations_mut()
93            .find(|(_, _, op)| op.operation_id.as_ref().unwrap() == operation_id)
94            .map(|(_, _, op)| op)
95    }
96
97    pub fn get_operation(&self, operation_id: &str) -> Option<(&Operation, &PathItem)> {
98        self.operations()
99            .find(|(_, _, op, _)| op.operation_id.as_ref().unwrap() == operation_id)
100            .map(|(_, _, op, item)| (op, item))
101    }
102
103    /// Merge another OpenAPI document into this one, keeping original schemas on conflict.
104    /// `a.merge(b)` will have all schemas from `a` and `b`, but keep `a` for any duplicates.
105    pub fn merge(mut self, other: OpenAPI) -> Result<Self, MergeError> {
106        merge_map(&mut self.info.extensions, other.info.extensions);
107
108        merge_vec(&mut self.servers, other.servers, |a, b| a.url == b.url);
109
110        for (path, item) in other.paths {
111            let item = item.into_item().ok_or_else(|| MergeError::new("PathItem references are not yet supported. Please opena n issue if you need this feature."))?;
112            if self.paths.paths.contains_key(&path) {
113                let self_item = self.paths.paths.get_mut(&path).unwrap().as_mut().ok_or_else(|| MergeError::new("PathItem references are not yet supported. Please open an issue if you need this feature."))?;
114                option_or(&mut self_item.get, item.get);
115                option_or(&mut self_item.put, item.put);
116                option_or(&mut self_item.post, item.post);
117                option_or(&mut self_item.delete, item.delete);
118                option_or(&mut self_item.options, item.options);
119                option_or(&mut self_item.head, item.head);
120                option_or(&mut self_item.patch, item.patch);
121                option_or(&mut self_item.trace, item.trace);
122
123                merge_vec(&mut self_item.servers, item.servers, |a, b| a.url == b.url);
124                merge_map(&mut self_item.extensions, item.extensions);
125
126                if self_item.parameters.len() != item.parameters.len() {
127                    return Err(MergeError(format!("PathItem {} parameters do not have the same length", path)));
128                }
129                for (a, b) in self_item.parameters.iter_mut().zip(item.parameters) {
130                    let a = a.as_item().ok_or_else(|| MergeError::new("Parameter references are not yet supported. Please open an issue if you need this feature."))?;
131                    let b = b.as_item().ok_or_else(|| MergeError::new("Parameter references are not yet supported. Please open an issue if you need this feature."))?;
132                    if a.name != b.name {
133                        return Err(MergeError(format!("PathItem {} parameter {} does not have the same name as {}", path, a.name, b.name)));
134                    }
135                }
136            } else {
137                self.paths.paths.insert(path, RefOr::Item(item));
138            }
139        }
140
141        merge_map(&mut self.components.extensions, other.components.extensions);
142        merge_map(&mut self.components.schemas, other.components.schemas.into());
143        merge_map(&mut self.components.responses, other.components.responses.into());
144        merge_map(&mut self.components.parameters, other.components.parameters.into());
145        merge_map(&mut self.components.examples, other.components.examples.into());
146        merge_map(&mut self.components.request_bodies, other.components.request_bodies.into());
147        merge_map(&mut self.components.headers, other.components.headers.into());
148        merge_map(&mut self.components.security_schemes, other.components.security_schemes.into());
149        merge_map(&mut self.components.links, other.components.links.into());
150        merge_map(&mut self.components.callbacks, other.components.callbacks.into());
151
152        merge_vec(&mut self.security, other.security, |a, b| {
153            if a.len() != b.len() {
154                return false;
155            }
156            a.iter().all(|(a, _)| b.contains_key(a))
157        });
158        merge_vec(&mut self.tags, other.tags, |a, b| a.name == b.name);
159
160        match self.external_docs.as_mut() {
161            Some(ext) => {
162                if let Some(other) = other.external_docs {
163                    merge_map(&mut ext.extensions, other.extensions)
164                }
165            }
166            None => self.external_docs = other.external_docs
167        }
168
169        merge_map(&mut self.extensions, other.extensions);
170        Ok(self)
171    }
172
173    /// Merge another OpenAPI document into this one, replacing any duplicate schemas.
174    /// `a.merge_overwrite(b)` will have all schemas from `a` and `b`, but keep `b` for any duplicates.
175    pub fn merge_overwrite(self, other: OpenAPI) -> Result<Self, MergeError> {
176        other.merge(self)
177    }
178}
179
180impl Default for OpenAPI {
181    fn default() -> Self {
182        // 3.1 is a backwards incompatible change that we don't support yet.
183        OpenAPI {
184            openapi: "3.0.3".to_string(),
185            info: default(),
186            servers: default(),
187            paths: default(),
188            components: default(),
189            security: default(),
190            tags: default(),
191            external_docs: default(),
192            extensions: default(),
193        }
194    }
195}
196
197fn merge_vec<T>(original: &mut Vec<T>, mut other: Vec<T>, cmp: fn(&T, &T) -> bool) {
198    other.retain(|o| !original.iter().any(|r| cmp(o, r)));
199    original.extend(other);
200}
201
202fn merge_map<K, V>(original: &mut IndexMap<K, V>, mut other: IndexMap<K, V>) where K: Eq + std::hash::Hash {
203    other.retain(|k, _| !original.contains_key(k));
204    original.extend(other);
205}
206
207fn option_or<T>(original: &mut Option<T>, other: Option<T>) {
208    if original.is_none() {
209        *original = other;
210    }
211}
212
213#[derive(Debug)]
214pub struct MergeError(String);
215
216impl MergeError {
217    pub fn new(msg: &str) -> Self {
218        MergeError(msg.to_string())
219    }
220}
221
222impl std::error::Error for MergeError {}
223
224impl std::fmt::Display for MergeError {
225    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
226        write!(f, "{}", self.0)
227    }
228}
229
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn test_merge_basic() {
237        let mut a = OpenAPI::default();
238        a.servers.push(Server {
239            url: "http://localhost".to_string(),
240            ..Server::default()
241        });
242        let mut b = OpenAPI::default();
243        b.servers.push(Server {
244            url: "http://localhost".to_string(),
245            ..Server::default()
246        });
247        a = a.merge(b).unwrap();
248        assert_eq!(a.servers.len(), 1);
249    }
250}