animi_okapi/
merge.rs

1use crate::openapi3::{Components, Info, OpenApi, PathItem, Responses, Tag};
2use crate::{Map, MapEntry};
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::fmt::Display;
6
7#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
8pub struct MergeError {
9    pub msg: String,
10}
11
12impl Display for MergeError {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        write!(f, "{}", self.msg)
15    }
16}
17
18impl MergeError {
19    fn new<S: AsRef<str>>(msg: S) -> Self {
20        MergeError {
21            msg: msg.as_ref().to_owned(),
22        }
23    }
24}
25
26impl OpenApi {
27    /// Merge the given OpenAPI spec into the current one.
28    pub fn merge_spec<S: Display>(mut self, path_prefix: &S, s2: &Self) -> Result<(), MergeError> {
29        merge_specs(&mut self, path_prefix, s2)
30    }
31}
32
33/// Marge the list of all specs together into on big OpenApi object.
34pub fn marge_spec_list<S: Display>(spec_list: &[(S, OpenApi)]) -> Result<OpenApi, MergeError> {
35    let mut openapi_docs = OpenApi::new();
36    for (path_prefix, spec) in spec_list {
37        merge_specs(&mut openapi_docs, path_prefix, spec)?;
38    }
39    Ok(openapi_docs)
40}
41
42/// Merge the given OpenAPI spec into the current one.
43pub fn merge_specs<S: Display>(
44    s1: &mut OpenApi,
45    path_prefix: &S,
46    s2: &OpenApi,
47) -> Result<(), MergeError> {
48    // Check if specs are same version
49    if s1.openapi != s2.openapi {
50        return Err(MergeError::new("OpenAPI specs version do not match."));
51    }
52    merge_spec_info(&mut s1.info, &s2.info)?;
53    merge_vec(&mut s1.servers, &s2.servers);
54    merge_paths(&mut s1.paths, path_prefix, &s2.paths)?;
55    merge_components(&mut s1.components, &s2.components)?;
56    // This is a `Vec<Map<String, _>` but just merge the `Vec` items together.
57    // Do not merge the `Map` items together.
58    merge_vec(&mut s1.security, &s2.security);
59    merge_tags(&mut s1.tags, &s2.tags)?;
60    // Replace the external_docs info as 1 block, so don't mix
61    merge_option(&mut s1.external_docs, &s2.external_docs);
62    merge_map(&mut s1.extensions, &s2.extensions, "extensions");
63    Ok(())
64}
65
66pub fn merge_spec_info(s1: &mut Info, s2: &Info) -> Result<(), MergeError> {
67    s1.title = merge_string(&s1.title, &s2.title);
68    merge_opt_string(&mut s1.description, &s2.description);
69    merge_opt_string(&mut s1.terms_of_service, &s2.terms_of_service);
70    // Replace the contact info as 1 block, so don't mix
71    merge_option(&mut s1.contact, &s2.contact);
72    // Replace the license info as 1 block, so don't mix
73    merge_option(&mut s1.license, &s2.license);
74    s1.version = merge_string(&s1.version, &s2.version);
75    merge_map(&mut s1.extensions, &s2.extensions, "extensions");
76    Ok(())
77}
78
79/// Merge `Map<String, PathItem>`/`&Map<String, PathItem>`:
80/// Merge together. If key already exists, use s1 version.
81/// Use `path_prefix` in order to specify the mounting points for the routes.
82pub fn merge_paths<S: Display>(
83    s1: &mut Map<String, PathItem>,
84    path_prefix: &S,
85    s2: &Map<String, PathItem>,
86) -> Result<(), MergeError> {
87    // Add all s2 values
88    // (if key does not already exists)
89    for (key, value) in s2 {
90        let new_key = if key.starts_with('/') {
91            format!("{}{}", path_prefix, key)
92        } else {
93            log::error!(
94                "All routes should have a leading '/' but non found in `{}`.",
95                key
96            );
97            format!("{}/{}", path_prefix, key)
98        };
99        match s1.entry(new_key) {
100            MapEntry::Occupied(mut entry) => {
101                // Merge `PathItem` so get/post/put routes are getting merged
102                let current_value = entry.get_mut();
103                merge_path_item(current_value, value)?;
104            }
105            MapEntry::Vacant(entry) => {
106                entry.insert(value.clone());
107            }
108        }
109    }
110    Ok(())
111}
112
113pub fn merge_path_item(s1: &mut PathItem, s2: &PathItem) -> Result<(), MergeError> {
114    merge_opt_string(&mut s1.reference, &s2.reference);
115    merge_opt_string(&mut s1.summary, &s2.summary);
116    merge_opt_string(&mut s1.description, &s2.description);
117
118    merge_option(&mut s1.get, &s2.get);
119    merge_option(&mut s1.put, &s2.put);
120    merge_option(&mut s1.post, &s2.post);
121    merge_option(&mut s1.delete, &s2.delete);
122    merge_option(&mut s1.options, &s2.options);
123    merge_option(&mut s1.head, &s2.head);
124    merge_option(&mut s1.patch, &s2.patch);
125    merge_option(&mut s1.trace, &s2.trace);
126
127    merge_option(&mut s1.servers, &s2.servers);
128    merge_vec(&mut s1.parameters, &s2.parameters);
129    merge_map(&mut s1.extensions, &s2.extensions, "extensions");
130    Ok(())
131}
132
133pub fn merge_components(
134    s1: &mut Option<Components>,
135    s2: &Option<Components>,
136) -> Result<(), MergeError> {
137    if s1.is_none() {
138        *s1 = s2.clone();
139        Ok(())
140    } else if s2.is_none() {
141        // Use/keep s1
142        Ok(())
143    } else {
144        if let Some(s1) = s1 {
145            let s2 = s2.as_ref().unwrap();
146            merge_map(&mut s1.schemas, &s2.schemas, "schemas");
147            merge_map(&mut s1.responses, &s2.responses, "responses");
148            merge_map(&mut s1.parameters, &s2.parameters, "parameters");
149            merge_map(&mut s1.examples, &s2.examples, "examples");
150            merge_map(&mut s1.request_bodies, &s2.request_bodies, "request_bodies");
151            merge_map(&mut s1.headers, &s2.headers, "headers");
152            merge_map(
153                &mut s1.security_schemes,
154                &s2.security_schemes,
155                "security_schemes",
156            );
157            merge_map(&mut s1.links, &s2.links, "links");
158            merge_map(&mut s1.callbacks, &s2.callbacks, "callbacks");
159            merge_map(&mut s1.extensions, &s2.extensions, "extensions");
160        }
161        Ok(())
162    }
163}
164
165pub fn merge_tags(s1: &mut Vec<Tag>, s2: &[Tag]) -> Result<Vec<Tag>, MergeError> {
166    // Create a `Map` so we can easily merge tag names.
167    let mut new_tags: Map<String, Tag> = Map::new();
168    // Add all s1 tags
169    for tag in s1 {
170        match new_tags.entry(tag.name.clone()) {
171            MapEntry::Occupied(mut entry) => {
172                let current_value = entry.get_mut();
173                merge_tag(current_value, tag)?;
174            }
175            MapEntry::Vacant(entry) => {
176                entry.insert(tag.clone());
177            }
178        }
179    }
180    // Add all s2 tags
181    for tag in s2 {
182        match new_tags.entry(tag.name.clone()) {
183            MapEntry::Occupied(mut entry) => {
184                let current_value = entry.get_mut();
185                merge_tag(current_value, tag)?;
186            }
187            MapEntry::Vacant(entry) => {
188                entry.insert(tag.clone());
189            }
190        }
191    }
192    // Convert `Map` to `Vec`
193    let mut new_tags_vec = Vec::new();
194
195    // Remove/add in order
196    // Clone all keys because that is faster then cloning all the tags.
197    // `BTreeMap` does not implement `pop()` so can not use that.
198    // Code below works both for `BTreeMap` as for `IndexMap`.
199    let keys: Vec<String> = new_tags.keys().cloned().collect();
200    for key in keys {
201        if let Some(tag) = new_tags.remove(&key) {
202            new_tags_vec.push(tag);
203        } else {
204            unreachable!("List sizes or same list do not match.");
205        }
206    }
207    Ok(new_tags_vec)
208}
209
210pub fn merge_tag(s1: &mut Tag, s2: &Tag) -> Result<(), MergeError> {
211    if s1.name != s2.name {
212        return Err(MergeError::new("Tried to merge Tags with different names."));
213    }
214    merge_opt_string(&mut s1.description, &s2.description);
215    merge_option(&mut s1.external_docs, &s2.external_docs);
216    merge_map(&mut s1.extensions, &s2.extensions, "extensions");
217    Ok(())
218}
219
220pub fn merge_responses(s1: &mut Responses, s2: &Responses) -> Result<(), MergeError> {
221    merge_option(&mut s1.default, &s2.default);
222    merge_map(&mut s1.responses, &s2.responses, "responses");
223    merge_map(&mut s1.extensions, &s2.extensions, "extensions");
224    Ok(())
225}
226
227/// Merge `String`/`&str`:
228/// - If one is empty: Use other
229/// - Otherwise: Use first value
230pub fn merge_string(s1: &str, s2: &str) -> String {
231    if s1.is_empty() {
232        s2.to_owned()
233    } else {
234        s1.to_owned()
235    }
236}
237
238/// Merge `Option<String>`/`&Option<String>`:
239/// - If one is `None`: Use other
240/// - If both are `Some`: Merge `String`
241/// - Otherwise: Use first value
242pub fn merge_opt_string(s1: &mut Option<String>, s2: &Option<String>) {
243    if s1.is_none() {
244        *s1 = s2.clone();
245    } else if s1.is_some() && s2.is_some() {
246        *s1 = Some(merge_string(s1.as_ref().unwrap(), s2.as_ref().unwrap()))
247    }
248}
249
250/// Merge `Option<T>`/`&Option<T>`:
251/// - If one is `None`: Use other
252/// - Otherwise: Use first value
253pub fn merge_option<T: Clone>(s1: &mut Option<T>, s2: &Option<T>) {
254    if s1.is_none() {
255        *s1 = s2.clone();
256    }
257}
258
259/// Merge `Map<String, _>`/`&Map<String, _>`:
260/// Merge together. If key already exists, use s1 version.
261pub fn merge_map<T: Clone + PartialEq + std::fmt::Debug>(
262    s1: &mut Map<String, T>,
263    s2: &Map<String, T>,
264    name: &str,
265) {
266    // Add all s2 values
267    // (if key does not already exists)
268    for (key, value) in s2 {
269        if let Some(s1_value) = s1.get(key) {
270            // Check if this is the same element
271            if value != s1_value {
272                log::warn!(
273                    "Found conflicting {} keys while merging, \
274                    they have the same name but different values for `{}`:\n\
275                    {:?}\n\
276                    {:?}",
277                    name,
278                    key,
279                    s1_value,
280                    value,
281                );
282            }
283        } else {
284            s1.insert(key.clone(), value.clone());
285        }
286    }
287}
288
289/// Merge `Vec<_>`/`&Vec<_>`:
290/// Append lists, `s1` first and `s2` after that.
291pub fn merge_vec<T: Clone>(s1: &mut Vec<T>, s2: &[T]) {
292    // Add all s2 values
293    for value in s2 {
294        s1.push(value.clone());
295    }
296}