golem_openapi_client_generator/
merger.rs1use indexmap::IndexMap;
2use openapiv3::{Components, ExternalDocumentation, OpenAPI, Paths};
3
4use crate::Error;
5use crate::Result;
6
7pub fn merge_all_openapi_specs(openapi_specs: Vec<OpenAPI>) -> Result<OpenAPI> {
8 if openapi_specs.is_empty() {
9 Err(Error::unexpected("No OpenAPI specs provided"))
10 } else if openapi_specs.len() == 1 {
11 Ok(openapi_specs.into_iter().next().unwrap())
12 } else {
13 let mut openapi_specs = openapi_specs;
14 let first = openapi_specs.pop().unwrap();
15 let rest = openapi_specs;
16 rest.into_iter().try_fold(first, merge_openapi_specs)
17 }
18}
19
20fn merge_openapi_specs(a: OpenAPI, b: OpenAPI) -> Result<OpenAPI> {
21 let openapi_version = {
22 if a.openapi != b.openapi {
23 return Err(Error::unexpected("OpenAPI versions do not match"));
24 }
25 a.openapi
26 };
27
28 let info = {
29 if a.info != b.info {
30 return Err(Error::unexpected("Info objects do not match"));
31 }
32 a.info
33 };
34
35 let servers = {
36 if a.servers != b.servers {
37 return Err(Error::unexpected("Servers do not match"));
38 }
39 a.servers
40 };
41
42 let all_tags = {
43 let a_tags_map = a
44 .tags
45 .into_iter()
46 .map(|tag| (tag.name.clone(), tag))
47 .collect::<IndexMap<_, _>>();
48 let b_tags_map = b
49 .tags
50 .into_iter()
51 .map(|tag| (tag.name.clone(), tag))
52 .collect::<IndexMap<_, _>>();
53 let merged = merge_unique(a_tags_map, b_tags_map)?;
54
55 merged.into_values().collect::<Vec<_>>()
56 };
57
58 let all_paths = {
59 let Paths {
60 paths: a_paths,
61 extensions: a_extensions,
62 } = a.paths;
63 let Paths {
64 paths: b_paths,
65 extensions: b_extensions,
66 } = b.paths;
67 let all_paths = merge_unique(a_paths, b_paths)?;
68 let all_extensions = merge_unique(a_extensions, b_extensions)?;
69 Paths {
70 paths: all_paths,
71 extensions: all_extensions,
72 }
73 };
74
75 let components = merge_components(a.components, b.components)?;
76 let security = merge_unique_option_list(a.security, b.security);
77 let extensions = merge_unique(a.extensions, b.extensions)?;
78
79 let external_docs = merge_external_docs(a.external_docs, b.external_docs)?;
80
81 let result = OpenAPI {
82 openapi: openapi_version,
83 info,
84 servers,
85 paths: all_paths,
86 components,
87 security,
88 tags: all_tags,
89 extensions,
90 external_docs,
91 };
92
93 Ok(result)
94}
95
96fn merge_components(a: Option<Components>, b: Option<Components>) -> Result<Option<Components>> {
97 let result = match (a, b) {
98 (Some(a), Some(b)) => {
99 let Components {
100 schemas: a_schemas,
101 responses: a_responses,
102 parameters: a_parameters,
103 examples: a_examples,
104 request_bodies: a_request_bodies,
105 headers: a_headers,
106 security_schemes: a_security_schemes,
107 links: a_links,
108 callbacks: a_callbacks,
109 extensions: a_extensions,
110 } = a;
111
112 let Components {
113 schemas: b_schemas,
114 responses: b_responses,
115 parameters: b_parameters,
116 examples: b_examples,
117 request_bodies: b_request_bodies,
118 headers: b_headers,
119 security_schemes: b_security_schemes,
120 links: b_links,
121 callbacks: b_callbacks,
122 extensions: b_extensions,
123 } = b;
124
125 let merged = Components {
126 schemas: merge_unique(a_schemas, b_schemas)?,
127 responses: merge_unique(a_responses, b_responses)?,
128 parameters: merge_unique(a_parameters, b_parameters)?,
129 examples: merge_unique(a_examples, b_examples)?,
130 request_bodies: merge_unique(a_request_bodies, b_request_bodies)?,
131 headers: merge_unique(a_headers, b_headers)?,
132 security_schemes: merge_unique(a_security_schemes, b_security_schemes)?,
133 links: merge_unique(a_links, b_links)?,
134 callbacks: merge_unique(a_callbacks, b_callbacks)?,
135 extensions: merge_unique(a_extensions, b_extensions)?,
136 };
137 Some(merged)
138 }
139 (Some(a), None) => Some(a),
140 (None, Some(b)) => Some(b),
141 (None, None) => None,
142 };
143
144 Ok(result)
145}
146
147fn merge_external_docs(
148 a: Option<ExternalDocumentation>,
149 b: Option<ExternalDocumentation>,
150) -> crate::Result<Option<ExternalDocumentation>> {
151 let result = match (a, b) {
152 (Some(a), Some(b)) => {
153 let ExternalDocumentation {
154 description: a_description,
155 url: a_url,
156 extensions: a_extensions,
157 } = a;
158
159 let ExternalDocumentation {
160 description: b_description,
161 url: b_url,
162 extensions: b_extensions,
163 } = b;
164
165 let description = match (a_description, b_description) {
166 (Some(a), Some(b)) => {
167 if a != b {
168 return Err(Error::unexpected(
169 "External documentation descriptions do not match",
170 ));
171 }
172 Some(a)
173 }
174 (Some(a), None) => Some(a),
175 (None, Some(b)) => Some(b),
176 (None, None) => None,
177 };
178
179 let url = {
180 if a_url != b_url {
181 return Err(Error::unexpected(
182 "External documentation URLs do not match",
183 ));
184 }
185 a_url
186 };
187
188 let extensions = merge_unique(a_extensions, b_extensions)?;
189
190 Some(ExternalDocumentation {
191 description,
192 url,
193 extensions,
194 })
195 }
196 (Some(a), None) => Some(a),
197 (None, Some(b)) => Some(b),
198 (None, None) => None,
199 };
200 Ok(result)
201}
202
203fn merge_unique_option_list<Key, Item>(
204 a: Option<Vec<IndexMap<Key, Item>>>,
205 b: Option<Vec<IndexMap<Key, Item>>>,
206) -> Option<Vec<IndexMap<Key, Item>>> {
207 match (a, b) {
208 (Some(a), Some(mut b)) => {
209 let mut result = a;
210 result.append(&mut b);
211 Some(result)
212 }
213 (Some(a), None) => Some(a),
214 (None, Some(b)) => Some(b),
215 (None, None) => None,
216 }
217}
218
219fn merge_unique<Key, Item>(
220 mut a: IndexMap<Key, Item>,
221 b: IndexMap<Key, Item>,
222) -> Result<IndexMap<Key, Item>>
223where
224 Key: std::fmt::Debug + Eq + std::hash::Hash,
225 Item: std::fmt::Debug + PartialEq,
226{
227 for (key, value) in b {
228 match a.entry(key) {
229 indexmap::map::Entry::Occupied(entry) => {
230 if entry.get() != &value {
231 return Err(Error::unexpected(format!(
232 "Duplicate key {:?} with different values \n Current {:?} \n New {:?}",
233 entry.key(),
234 entry.get(),
235 value
236 )));
237 }
238 }
239 indexmap::map::Entry::Vacant(entry) => {
240 entry.insert(value);
241 }
242 }
243 }
244 Ok(a)
245}