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}