1use serde::{Deserialize, Serialize};
6
7use crate::{
8 Callback, Content, Example, Header, Link, Parameter, PathItem, PropMap, RefOr, RequestBody,
9 Response, 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", default)]
93 pub media_types: PropMap<String, RefOr<Content>>,
94
95 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
97 pub extensions: PropMap<String, serde_json::Value>,
98}
99
100impl Components {
101 #[must_use]
104 pub fn new() -> Self {
105 Default::default()
106 }
107
108 #[must_use]
115 pub fn add_security_scheme<N: Into<String>, S: Into<SecurityScheme>>(
116 mut self,
117 name: N,
118 security_scheme: S,
119 ) -> Self {
120 self.security_schemes
121 .insert(name.into(), security_scheme.into());
122
123 self
124 }
125
126 #[must_use]
133 pub fn extend_security_schemes<
134 I: IntoIterator<Item = (N, S)>,
135 N: Into<String>,
136 S: Into<SecurityScheme>,
137 >(
138 mut self,
139 schemas: I,
140 ) -> Self {
141 self.security_schemes.extend(
142 schemas
143 .into_iter()
144 .map(|(name, item)| (name.into(), item.into())),
145 );
146 self
147 }
148
149 #[must_use]
153 pub fn add_schema<S: Into<String>, I: Into<RefOr<Schema>>>(
154 mut self,
155 name: S,
156 schema: I,
157 ) -> Self {
158 self.schemas.insert(name, schema);
159 self
160 }
161
162 #[must_use]
177 pub fn extend_schemas<I, C, S>(mut self, schemas: I) -> Self
178 where
179 I: IntoIterator<Item = (S, C)>,
180 C: Into<RefOr<Schema>>,
181 S: Into<String>,
182 {
183 self.schemas.extend(
184 schemas
185 .into_iter()
186 .map(|(name, schema)| (name.into(), schema.into())),
187 );
188 self
189 }
190
191 #[must_use]
193 pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
194 mut self,
195 name: S,
196 response: R,
197 ) -> Self {
198 self.responses.insert(name.into(), response.into());
199 self
200 }
201
202 #[must_use]
204 pub fn extend_responses<
205 I: IntoIterator<Item = (S, R)>,
206 S: Into<String>,
207 R: Into<RefOr<Response>>,
208 >(
209 mut self,
210 responses: I,
211 ) -> Self {
212 self.responses.extend(
213 responses
214 .into_iter()
215 .map(|(name, response)| (name.into(), response.into())),
216 );
217 self
218 }
219
220 #[must_use]
222 pub fn add_parameter<N: Into<String>, P: Into<RefOr<Parameter>>>(
223 mut self,
224 name: N,
225 parameter: P,
226 ) -> Self {
227 self.parameters.insert(name.into(), parameter.into());
228 self
229 }
230
231 #[must_use]
233 pub fn add_example<N: Into<String>, E: Into<RefOr<Example>>>(
234 mut self,
235 name: N,
236 example: E,
237 ) -> Self {
238 self.examples.insert(name.into(), example.into());
239 self
240 }
241
242 #[must_use]
244 pub fn add_request_body<N: Into<String>, B: Into<RefOr<RequestBody>>>(
245 mut self,
246 name: N,
247 request_body: B,
248 ) -> Self {
249 self.request_bodies.insert(name.into(), request_body.into());
250 self
251 }
252
253 #[must_use]
255 pub fn add_header<N: Into<String>, H: Into<RefOr<Header>>>(
256 mut self,
257 name: N,
258 header: H,
259 ) -> Self {
260 self.headers.insert(name.into(), header.into());
261 self
262 }
263
264 #[must_use]
266 pub fn add_link<N: Into<String>, L: Into<RefOr<Link>>>(mut self, name: N, link: L) -> Self {
267 self.links.insert(name.into(), link.into());
268 self
269 }
270
271 #[must_use]
273 pub fn add_callback<N: Into<String>, C: Into<RefOr<Callback>>>(
274 mut self,
275 name: N,
276 callback: C,
277 ) -> Self {
278 self.callbacks.insert(name.into(), callback.into());
279 self
280 }
281
282 #[must_use]
287 pub fn add_path_item<N: Into<String>, P: Into<RefOr<PathItem>>>(
288 mut self,
289 name: N,
290 path_item: P,
291 ) -> Self {
292 self.path_items.insert(name.into(), path_item.into());
293 self
294 }
295
296 #[must_use]
302 pub fn add_media_type<N: Into<String>, C: Into<RefOr<Content>>>(
303 mut self,
304 name: N,
305 media_type: C,
306 ) -> Self {
307 self.media_types.insert(name.into(), media_type.into());
308 self
309 }
310
311 pub fn append(&mut self, other: &mut Self) {
316 other
317 .schemas
318 .retain(|name, _| !self.schemas.contains_key(name));
319 self.schemas.append(&mut other.schemas);
320
321 other
322 .responses
323 .retain(|name, _| !self.responses.contains_key(name));
324 self.responses.append(&mut other.responses);
325
326 other
327 .parameters
328 .retain(|name, _| !self.parameters.contains_key(name));
329 self.parameters.append(&mut other.parameters);
330
331 other
332 .examples
333 .retain(|name, _| !self.examples.contains_key(name));
334 self.examples.append(&mut other.examples);
335
336 other
337 .request_bodies
338 .retain(|name, _| !self.request_bodies.contains_key(name));
339 self.request_bodies.append(&mut other.request_bodies);
340
341 other
342 .headers
343 .retain(|name, _| !self.headers.contains_key(name));
344 self.headers.append(&mut other.headers);
345
346 other
347 .security_schemes
348 .retain(|name, _| !self.security_schemes.contains_key(name));
349 self.security_schemes.append(&mut other.security_schemes);
350
351 other.links.retain(|name, _| !self.links.contains_key(name));
352 self.links.append(&mut other.links);
353
354 other
355 .callbacks
356 .retain(|name, _| !self.callbacks.contains_key(name));
357 self.callbacks.append(&mut other.callbacks);
358
359 other
360 .path_items
361 .retain(|name, _| !self.path_items.contains_key(name));
362 self.path_items.append(&mut other.path_items);
363
364 other
365 .media_types
366 .retain(|name, _| !self.media_types.contains_key(name));
367 self.media_types.append(&mut other.media_types);
368
369 other
370 .extensions
371 .retain(|name, _| !self.extensions.contains_key(name));
372 self.extensions.append(&mut other.extensions);
373 }
374
375 #[must_use]
377 pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
378 self.extensions = extensions;
379 self
380 }
381
382 #[must_use]
384 pub fn is_empty(&self) -> bool {
385 self.schemas.is_empty()
386 && self.responses.is_empty()
387 && self.parameters.is_empty()
388 && self.examples.is_empty()
389 && self.request_bodies.is_empty()
390 && self.headers.is_empty()
391 && self.security_schemes.is_empty()
392 && self.links.is_empty()
393 && self.callbacks.is_empty()
394 && self.path_items.is_empty()
395 && self.media_types.is_empty()
396 && self.extensions.is_empty()
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use assert_json_diff::assert_json_eq;
403 use serde_json::json;
404
405 use super::*;
406 use crate::{Operation, ParameterIn, PathItemType, Ref};
407
408 #[test]
409 fn empty_components_serializes_with_no_fields() {
410 assert_json_eq!(Components::new(), json!({}));
411 assert!(Components::new().is_empty());
412 }
413
414 #[test]
415 fn each_new_field_serializes_under_spec_name() {
416 let components = Components::new()
417 .add_parameter(
418 "PageParam",
419 Parameter::new("page").location(ParameterIn::Query),
420 )
421 .add_example(
422 "PetExample",
423 RefOr::Ref(Ref::new("#/components/examples/UpstreamPet")),
424 )
425 .add_request_body("PetBody", RequestBody::new())
426 .add_header("X-Rate-Limit", Header::default())
427 .add_link("GetPetLink", Link::default())
428 .add_callback(
429 "OrderShipped",
430 Callback::new().path(
431 "{$request.body#/callbackUrl}",
432 PathItem::new(PathItemType::Post, Operation::new()),
433 ),
434 )
435 .add_path_item(
436 "PingWebhook",
437 PathItem::new(PathItemType::Post, Operation::new()),
438 );
439
440 let value = serde_json::to_value(&components).expect("serialize");
441
442 assert!(value.get("parameters").is_some(), "expected parameters");
443 assert!(value.get("examples").is_some(), "expected examples");
444 assert!(
445 value.get("requestBodies").is_some(),
446 "expected requestBodies (camelCase)"
447 );
448 assert!(value.get("headers").is_some(), "expected headers");
449 assert!(value.get("links").is_some(), "expected links");
450 assert!(value.get("callbacks").is_some(), "expected callbacks");
451 assert!(
452 value.get("pathItems").is_some(),
453 "expected pathItems (camelCase, OAS 3.1)"
454 );
455 }
456
457 #[test]
458 fn is_empty_recognizes_each_new_field() {
459 assert!(
461 !Components::new()
462 .add_parameter("p", Parameter::new("q"))
463 .is_empty()
464 );
465 assert!(
466 !Components::new()
467 .add_example("e", crate::Example::default())
468 .is_empty()
469 );
470 assert!(
471 !Components::new()
472 .add_request_body("rb", RequestBody::new())
473 .is_empty()
474 );
475 assert!(
476 !Components::new()
477 .add_header("h", Header::default())
478 .is_empty()
479 );
480 assert!(!Components::new().add_link("l", Link::default()).is_empty());
481 assert!(
482 !Components::new()
483 .add_callback("cb", Callback::new())
484 .is_empty()
485 );
486 assert!(
487 !Components::new()
488 .add_path_item("pi", PathItem::new(PathItemType::Get, Operation::new()))
489 .is_empty()
490 );
491 }
492
493 #[test]
494 fn append_preserves_self_on_key_collision() {
495 let mut a = Components::new().add_parameter("dup", Parameter::new("a_param"));
496 let mut b = Components::new()
497 .add_parameter("dup", Parameter::new("b_param"))
498 .add_parameter("only_b", Parameter::new("b_only_param"));
499
500 a.append(&mut b);
501
502 let dup = a.parameters.get("dup").expect("dup retained");
503 match dup {
504 RefOr::Type(p) => assert_eq!(p.name, "a_param", "self's value should win on collision"),
505 RefOr::Ref(_) => panic!("unexpected ref"),
506 }
507 assert!(a.parameters.contains_key("only_b"));
508 }
509}