openapi_merge/
ext.rs

1use openapiv3::OpenAPI;
2use serde_json as json;
3
4use super::*;
5
6pub trait OpenAPIExt {
7    fn merge_components(&mut self, components: Option<openapiv3::Components>);
8    fn merge_security(&mut self, security: Option<Vec<openapiv3::SecurityRequirement>>);
9    fn merge_tags(&mut self, tags: Vec<openapiv3::Tag>);
10    fn merge_extensions(&mut self, extensions: indexmap::IndexMap<String, json::Value>);
11    fn merge_operation(&mut self, path: &str, method: &str, operation: &openapiv3::Operation);
12}
13
14impl OpenAPIExt for OpenAPI {
15    fn merge_components(&mut self, components: Option<openapiv3::Components>) {
16        if let Some(components) = components {
17            let base = self.components.get_or_insert_with(default);
18            base.schemas.merge(components.schemas);
19            base.responses.merge(components.responses);
20            base.parameters.merge(components.parameters);
21            base.examples.merge(components.examples);
22            base.request_bodies.merge(components.request_bodies);
23            base.headers.merge(components.headers);
24            base.security_schemes.merge(components.security_schemes);
25            base.links.merge(components.links);
26            base.callbacks.merge(components.callbacks);
27            base.extensions.merge(components.extensions);
28        }
29    }
30
31    fn merge_security(&mut self, security: Option<Vec<openapiv3::SecurityRequirement>>) {
32        if let Some(security) = security {
33            tracing::warn!("Merging {:?} is not supported yet", security);
34        }
35    }
36
37    fn merge_tags(&mut self, tags: Vec<openapiv3::Tag>) {
38        for tag in tags {
39            if !self.tags.contains(&tag) {
40                self.tags.push(tag)
41            }
42        }
43    }
44
45    fn merge_extensions(&mut self, extensions: indexmap::IndexMap<String, serde_json::Value>) {
46        self.extensions.merge(extensions)
47    }
48
49    fn merge_operation(&mut self, path: &str, method: &str, operation: &openapiv3::Operation) {
50        tracing::info!(path, method, operation.summary, "Merging operation");
51        let paths = &mut self.paths.paths;
52
53        if paths.contains_key(path) {
54            tracing::warn!(path, "already found in self");
55        }
56        let item = paths
57            .entry(path.into())
58            .or_insert_with(|| openapiv3::ReferenceOr::Item(openapiv3::PathItem::default()));
59        update_item(item, method, operation);
60    }
61}
62
63fn update_item(
64    item: &mut openapiv3::ReferenceOr<openapiv3::PathItem>,
65    method: &str,
66    operation: &openapiv3::Operation,
67) {
68    match item {
69        openapiv3::ReferenceOr::Reference { reference } => {
70            tracing::warn!(reference, "Cannot modify reference")
71        }
72        openapiv3::ReferenceOr::Item(item) => update_path_item(item, method, operation),
73    }
74}
75
76fn update_path_item(
77    item: &mut openapiv3::PathItem,
78    method: &str,
79    operation: &openapiv3::Operation,
80) {
81    if let Some(op) = item.get_mut(method) {
82        if op.is_none() {
83            *op = Some(operation.clone());
84        } else {
85            tracing::warn!(method, "Cannot replace existing operation");
86        }
87    }
88}
89
90trait Method {
91    fn get_mut(&mut self, method: &str) -> Option<&mut Option<openapiv3::Operation>>;
92}
93
94impl Method for openapiv3::PathItem {
95    fn get_mut(&mut self, method: &str) -> Option<&mut Option<openapiv3::Operation>> {
96        match method.to_ascii_lowercase().as_str() {
97            "get" => Some(&mut self.get),
98            "put" => Some(&mut self.put),
99            "post" => Some(&mut self.post),
100            "delete" => Some(&mut self.delete),
101            "options" => Some(&mut self.options),
102            "head" => Some(&mut self.head),
103            "patch" => Some(&mut self.patch),
104            "trace" => Some(&mut self.trace),
105            other => {
106                tracing::warn!(method = other, "Skipping unsupported");
107                None
108            }
109        }
110    }
111}
112
113trait Merge {
114    fn merge(&mut self, other: Self);
115}
116
117impl<T> Merge for indexmap::IndexMap<String, T> {
118    fn merge(&mut self, other: Self) {
119        other.into_iter().for_each(|(key, value)| {
120            self.entry(key).or_insert(value);
121        });
122    }
123}