salvo_oapi/openapi/components.rs
1//! Implements [OpenAPI Schema Object][schema] types which can be
2//! used to define field properties, enum values, array or object types.
3//!
4//! [schema]: https://spec.openapis.org/oas/latest.html#schema-object
5use serde::{Deserialize, Serialize};
6
7use crate::{PropMap, RefOr, Response, Responses, Schema, Schemas, SecurityScheme};
8
9/// Implements [OpenAPI Components Object][components] which holds supported
10/// reusable objects.
11///
12/// Components can hold either reusable types themselves or references to other reusable
13/// types.
14///
15/// [components]: https://spec.openapis.org/oas/latest.html#components-object
16#[non_exhaustive]
17#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
18#[serde(rename_all = "camelCase")]
19pub struct Components {
20 /// Map of reusable [OpenAPI Schema Object][schema]s.
21 ///
22 /// [schema]: https://spec.openapis.org/oas/latest.html#schema-object
23 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
24 pub schemas: Schemas,
25
26 /// Map of reusable response name, to [OpenAPI Response Object][response]s or [OpenAPI
27 /// Reference][reference]s to [OpenAPI Response Object][response]s.
28 ///
29 /// [response]: https://spec.openapis.org/oas/latest.html#response-object
30 /// [reference]: https://spec.openapis.org/oas/latest.html#reference-object
31 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
32 pub responses: Responses,
33
34 /// Map of reusable [OpenAPI Security Scheme Object][security_scheme]s.
35 ///
36 /// [security_scheme]: https://spec.openapis.org/oas/latest.html#security-scheme-object
37 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
38 pub security_schemes: PropMap<String, SecurityScheme>,
39
40 /// Optional extensions "x-something"
41 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
42 pub extensions: PropMap<String, serde_json::Value>,
43}
44
45impl Components {
46 /// Construct a new empty [`Components`]. This is effectively same as calling
47 /// [`Components::default`].
48 #[must_use]
49 pub fn new() -> Self {
50 Default::default()
51 }
52
53 /// Add [`SecurityScheme`] to [`Components`] and returns `Self`.
54 ///
55 /// Accepts two arguments where first is the name of the [`SecurityScheme`]. This is later when
56 /// referenced by [`SecurityRequirement`][requirement]s. Second parameter is the
57 /// [`SecurityScheme`].
58 ///
59 /// [requirement]: crate::SecurityRequirement
60 #[must_use]
61 pub fn add_security_scheme<N: Into<String>, S: Into<SecurityScheme>>(
62 mut self,
63 name: N,
64 security_scheme: S,
65 ) -> Self {
66 self.security_schemes
67 .insert(name.into(), security_scheme.into());
68
69 self
70 }
71
72 /// Add iterator of [`SecurityScheme`]s to [`Components`].
73 ///
74 /// Accepts two arguments where first is the name of the [`SecurityScheme`]. This is later when
75 /// referenced by [`SecurityRequirement`][requirement]s. Second parameter is the
76 /// [`SecurityScheme`].
77 ///
78 /// [requirement]: crate::SecurityRequirement
79 #[must_use]
80 pub fn extend_security_schemes<
81 I: IntoIterator<Item = (N, S)>,
82 N: Into<String>,
83 S: Into<SecurityScheme>,
84 >(
85 mut self,
86 schemas: I,
87 ) -> Self {
88 self.security_schemes.extend(
89 schemas
90 .into_iter()
91 .map(|(name, item)| (name.into(), item.into())),
92 );
93 self
94 }
95
96 /// Add [`Schema`] to [`Components`] and returns `Self`.
97 ///
98 /// Accepts two arguments where first is name of the schema and second is the schema itself.
99 #[must_use]
100 pub fn add_schema<S: Into<String>, I: Into<RefOr<Schema>>>(
101 mut self,
102 name: S,
103 schema: I,
104 ) -> Self {
105 self.schemas.insert(name, schema);
106 self
107 }
108
109 /// Add [`Schema`]s from iterator.
110 ///
111 /// # Examples
112 /// ```
113 /// # use salvo_oapi::{Components, Object, BasicType, Schema};
114 /// Components::new().extend_schemas([(
115 /// "Pet",
116 /// Schema::from(
117 /// Object::new()
118 /// .property("name", Object::new().schema_type(BasicType::String))
119 /// .required("name"),
120 /// ),
121 /// )]);
122 /// ```
123 #[must_use]
124 pub fn extend_schemas<I, C, S>(mut self, schemas: I) -> Self
125 where
126 I: IntoIterator<Item = (S, C)>,
127 C: Into<RefOr<Schema>>,
128 S: Into<String>,
129 {
130 self.schemas.extend(
131 schemas
132 .into_iter()
133 .map(|(name, schema)| (name.into(), schema.into())),
134 );
135 self
136 }
137
138 /// Add a new response and returns `self`.
139 #[must_use]
140 pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
141 mut self,
142 name: S,
143 response: R,
144 ) -> Self {
145 self.responses.insert(name.into(), response.into());
146 self
147 }
148
149 /// Extends responses with the contents of an iterator.
150 #[must_use]
151 pub fn extend_responses<
152 I: IntoIterator<Item = (S, R)>,
153 S: Into<String>,
154 R: Into<RefOr<Response>>,
155 >(
156 mut self,
157 responses: I,
158 ) -> Self {
159 self.responses.extend(
160 responses
161 .into_iter()
162 .map(|(name, response)| (name.into(), response.into())),
163 );
164 self
165 }
166
167 /// Moves all elements from `other` into `self`, leaving `other` empty.
168 ///
169 /// If a key from `other` is already present in `self`, the respective
170 /// value from `self` will be overwritten with the respective value from `other`.
171 pub fn append(&mut self, other: &mut Self) {
172 other
173 .schemas
174 .retain(|name, _| !self.schemas.contains_key(name));
175 self.schemas.append(&mut other.schemas);
176
177 other
178 .responses
179 .retain(|name, _| !self.responses.contains_key(name));
180 self.responses.append(&mut other.responses);
181
182 other
183 .security_schemes
184 .retain(|name, _| !self.security_schemes.contains_key(name));
185 self.security_schemes.append(&mut other.security_schemes);
186
187 other
188 .extensions
189 .retain(|name, _| !self.extensions.contains_key(name));
190 self.extensions.append(&mut other.extensions);
191 }
192
193 /// Add openapi extensions (`x-something`) for [`Components`].
194 #[must_use]
195 pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
196 self.extensions = extensions;
197 self
198 }
199
200 /// Returns `true` if instance contains no elements.
201 #[must_use]
202 pub fn is_empty(&self) -> bool {
203 self.schemas.is_empty()
204 && self.responses.is_empty()
205 && self.security_schemes.is_empty()
206 && self.extensions.is_empty()
207 }
208}