1use serde::{Deserialize, Serialize};
6
7use crate::{
8 Callback, Example, Header, Link, Parameter, PathItem, PropMap, RefOr, RequestBody, Response,
9 Responses, Schema, Schemas, SecurityScheme,
10};
11
12#[non_exhaustive]
20#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
21#[serde(rename_all = "camelCase")]
22pub struct Components {
23 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
27 pub schemas: Schemas,
28
29 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
35 pub responses: Responses,
36
37 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
41 pub parameters: PropMap<String, RefOr<Parameter>>,
42
43 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
47 pub examples: PropMap<String, RefOr<Example>>,
48
49 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
53 pub request_bodies: PropMap<String, RefOr<RequestBody>>,
54
55 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
59 pub headers: PropMap<String, RefOr<Header>>,
60
61 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
65 pub security_schemes: PropMap<String, SecurityScheme>,
66
67 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
71 pub links: PropMap<String, RefOr<Link>>,
72
73 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
77 pub callbacks: PropMap<String, RefOr<Callback>>,
78
79 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
84 pub path_items: PropMap<String, RefOr<PathItem>>,
85
86 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
88 pub extensions: PropMap<String, serde_json::Value>,
89}
90
91impl Components {
92 #[must_use]
95 pub fn new() -> Self {
96 Default::default()
97 }
98
99 #[must_use]
106 pub fn add_security_scheme<N: Into<String>, S: Into<SecurityScheme>>(
107 mut self,
108 name: N,
109 security_scheme: S,
110 ) -> Self {
111 self.security_schemes
112 .insert(name.into(), security_scheme.into());
113
114 self
115 }
116
117 #[must_use]
124 pub fn extend_security_schemes<
125 I: IntoIterator<Item = (N, S)>,
126 N: Into<String>,
127 S: Into<SecurityScheme>,
128 >(
129 mut self,
130 schemas: I,
131 ) -> Self {
132 self.security_schemes.extend(
133 schemas
134 .into_iter()
135 .map(|(name, item)| (name.into(), item.into())),
136 );
137 self
138 }
139
140 #[must_use]
144 pub fn add_schema<S: Into<String>, I: Into<RefOr<Schema>>>(
145 mut self,
146 name: S,
147 schema: I,
148 ) -> Self {
149 self.schemas.insert(name, schema);
150 self
151 }
152
153 #[must_use]
168 pub fn extend_schemas<I, C, S>(mut self, schemas: I) -> Self
169 where
170 I: IntoIterator<Item = (S, C)>,
171 C: Into<RefOr<Schema>>,
172 S: Into<String>,
173 {
174 self.schemas.extend(
175 schemas
176 .into_iter()
177 .map(|(name, schema)| (name.into(), schema.into())),
178 );
179 self
180 }
181
182 #[must_use]
184 pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
185 mut self,
186 name: S,
187 response: R,
188 ) -> Self {
189 self.responses.insert(name.into(), response.into());
190 self
191 }
192
193 #[must_use]
195 pub fn extend_responses<
196 I: IntoIterator<Item = (S, R)>,
197 S: Into<String>,
198 R: Into<RefOr<Response>>,
199 >(
200 mut self,
201 responses: I,
202 ) -> Self {
203 self.responses.extend(
204 responses
205 .into_iter()
206 .map(|(name, response)| (name.into(), response.into())),
207 );
208 self
209 }
210
211 #[must_use]
213 pub fn add_parameter<N: Into<String>, P: Into<RefOr<Parameter>>>(
214 mut self,
215 name: N,
216 parameter: P,
217 ) -> Self {
218 self.parameters.insert(name.into(), parameter.into());
219 self
220 }
221
222 #[must_use]
224 pub fn add_example<N: Into<String>, E: Into<RefOr<Example>>>(
225 mut self,
226 name: N,
227 example: E,
228 ) -> Self {
229 self.examples.insert(name.into(), example.into());
230 self
231 }
232
233 #[must_use]
235 pub fn add_request_body<N: Into<String>, B: Into<RefOr<RequestBody>>>(
236 mut self,
237 name: N,
238 request_body: B,
239 ) -> Self {
240 self.request_bodies.insert(name.into(), request_body.into());
241 self
242 }
243
244 #[must_use]
246 pub fn add_header<N: Into<String>, H: Into<RefOr<Header>>>(
247 mut self,
248 name: N,
249 header: H,
250 ) -> Self {
251 self.headers.insert(name.into(), header.into());
252 self
253 }
254
255 #[must_use]
257 pub fn add_link<N: Into<String>, L: Into<RefOr<Link>>>(mut self, name: N, link: L) -> Self {
258 self.links.insert(name.into(), link.into());
259 self
260 }
261
262 #[must_use]
264 pub fn add_callback<N: Into<String>, C: Into<RefOr<Callback>>>(
265 mut self,
266 name: N,
267 callback: C,
268 ) -> Self {
269 self.callbacks.insert(name.into(), callback.into());
270 self
271 }
272
273 #[must_use]
278 pub fn add_path_item<N: Into<String>, P: Into<RefOr<PathItem>>>(
279 mut self,
280 name: N,
281 path_item: P,
282 ) -> Self {
283 self.path_items.insert(name.into(), path_item.into());
284 self
285 }
286
287 pub fn append(&mut self, other: &mut Self) {
292 other
293 .schemas
294 .retain(|name, _| !self.schemas.contains_key(name));
295 self.schemas.append(&mut other.schemas);
296
297 other
298 .responses
299 .retain(|name, _| !self.responses.contains_key(name));
300 self.responses.append(&mut other.responses);
301
302 other
303 .parameters
304 .retain(|name, _| !self.parameters.contains_key(name));
305 self.parameters.append(&mut other.parameters);
306
307 other
308 .examples
309 .retain(|name, _| !self.examples.contains_key(name));
310 self.examples.append(&mut other.examples);
311
312 other
313 .request_bodies
314 .retain(|name, _| !self.request_bodies.contains_key(name));
315 self.request_bodies.append(&mut other.request_bodies);
316
317 other
318 .headers
319 .retain(|name, _| !self.headers.contains_key(name));
320 self.headers.append(&mut other.headers);
321
322 other
323 .security_schemes
324 .retain(|name, _| !self.security_schemes.contains_key(name));
325 self.security_schemes.append(&mut other.security_schemes);
326
327 other.links.retain(|name, _| !self.links.contains_key(name));
328 self.links.append(&mut other.links);
329
330 other
331 .callbacks
332 .retain(|name, _| !self.callbacks.contains_key(name));
333 self.callbacks.append(&mut other.callbacks);
334
335 other
336 .path_items
337 .retain(|name, _| !self.path_items.contains_key(name));
338 self.path_items.append(&mut other.path_items);
339
340 other
341 .extensions
342 .retain(|name, _| !self.extensions.contains_key(name));
343 self.extensions.append(&mut other.extensions);
344 }
345
346 #[must_use]
348 pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
349 self.extensions = extensions;
350 self
351 }
352
353 #[must_use]
355 pub fn is_empty(&self) -> bool {
356 self.schemas.is_empty()
357 && self.responses.is_empty()
358 && self.parameters.is_empty()
359 && self.examples.is_empty()
360 && self.request_bodies.is_empty()
361 && self.headers.is_empty()
362 && self.security_schemes.is_empty()
363 && self.links.is_empty()
364 && self.callbacks.is_empty()
365 && self.path_items.is_empty()
366 && self.extensions.is_empty()
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use assert_json_diff::assert_json_eq;
373 use serde_json::json;
374
375 use super::*;
376 use crate::{Operation, ParameterIn, PathItemType, Ref};
377
378 #[test]
379 fn empty_components_serializes_with_no_fields() {
380 assert_json_eq!(Components::new(), json!({}));
381 assert!(Components::new().is_empty());
382 }
383
384 #[test]
385 fn each_new_field_serializes_under_spec_name() {
386 let components = Components::new()
387 .add_parameter(
388 "PageParam",
389 Parameter::new("page").location(ParameterIn::Query),
390 )
391 .add_example(
392 "PetExample",
393 RefOr::Ref(Ref::new("#/components/examples/UpstreamPet")),
394 )
395 .add_request_body("PetBody", RequestBody::new())
396 .add_header("X-Rate-Limit", Header::default())
397 .add_link("GetPetLink", Link::default())
398 .add_callback(
399 "OrderShipped",
400 Callback::new().path(
401 "{$request.body#/callbackUrl}",
402 PathItem::new(PathItemType::Post, Operation::new()),
403 ),
404 )
405 .add_path_item(
406 "PingWebhook",
407 PathItem::new(PathItemType::Post, Operation::new()),
408 );
409
410 let value = serde_json::to_value(&components).expect("serialize");
411
412 assert!(value.get("parameters").is_some(), "expected parameters");
413 assert!(value.get("examples").is_some(), "expected examples");
414 assert!(
415 value.get("requestBodies").is_some(),
416 "expected requestBodies (camelCase)"
417 );
418 assert!(value.get("headers").is_some(), "expected headers");
419 assert!(value.get("links").is_some(), "expected links");
420 assert!(value.get("callbacks").is_some(), "expected callbacks");
421 assert!(
422 value.get("pathItems").is_some(),
423 "expected pathItems (camelCase, OAS 3.1)"
424 );
425 }
426
427 #[test]
428 fn is_empty_recognizes_each_new_field() {
429 assert!(
431 !Components::new()
432 .add_parameter("p", Parameter::new("q"))
433 .is_empty()
434 );
435 assert!(
436 !Components::new()
437 .add_example("e", crate::Example::default())
438 .is_empty()
439 );
440 assert!(
441 !Components::new()
442 .add_request_body("rb", RequestBody::new())
443 .is_empty()
444 );
445 assert!(
446 !Components::new()
447 .add_header("h", Header::default())
448 .is_empty()
449 );
450 assert!(!Components::new().add_link("l", Link::default()).is_empty());
451 assert!(
452 !Components::new()
453 .add_callback("cb", Callback::new())
454 .is_empty()
455 );
456 assert!(
457 !Components::new()
458 .add_path_item("pi", PathItem::new(PathItemType::Get, Operation::new()))
459 .is_empty()
460 );
461 }
462
463 #[test]
464 fn append_preserves_self_on_key_collision() {
465 let mut a = Components::new().add_parameter("dup", Parameter::new("a_param"));
466 let mut b = Components::new()
467 .add_parameter("dup", Parameter::new("b_param"))
468 .add_parameter("only_b", Parameter::new("b_only_param"));
469
470 a.append(&mut b);
471
472 let dup = a.parameters.get("dup").expect("dup retained");
473 match dup {
474 RefOr::Type(p) => assert_eq!(p.name, "a_param", "self's value should win on collision"),
475 RefOr::Ref(_) => panic!("unexpected ref"),
476 }
477 assert!(a.parameters.contains_key("only_b"));
478 }
479}