Skip to main content

salvo_oapi/openapi/
info.rs

1//! Implements [OpenAPI Metadata][info] types.
2//!
3//! Refer to [`OpenApi`][openapi_trait] trait and [derive documentation][derive]
4//! for examples and usage details.
5//!
6//! [info]: <https://spec.openapis.org/oas/latest.html#info-object>
7//! [openapi_trait]: ../../trait.OpenApi.html
8//! [derive]: ../../derive.OpenApi.html
9
10use serde::{Deserialize, Serialize};
11
12use crate::PropMap;
13
14/// # Examples
15///
16/// Create [`Info`]].
17/// ```
18/// # use salvo_oapi::{Info, Contact};
19/// let info = Info::new("My api", "1.0.0")
20///     .contact(Contact::new().name("Admin Admin").email("amdin@petapi.com"));
21/// ```
22/// OpenAPI [Info][info] object represents metadata of the API.
23///
24/// You can use [`Info::new`] to construct a new [`Info`] object.
25///
26/// [info]: <https://spec.openapis.org/oas/latest.html#info-object>
27#[non_exhaustive]
28#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
29#[serde(rename_all = "camelCase")]
30pub struct Info {
31    /// Title of the API.
32    pub title: String,
33
34    /// Optional short summary of the API. Added in OpenAPI 3.1.
35    ///
36    /// See <https://spec.openapis.org/oas/v3.2.0.html#info-object>.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub summary: Option<String>,
39
40    /// Optional description of the API.
41    ///
42    /// Value supports markdown syntax.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub description: Option<String>,
45
46    /// Optional url for terms of service.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub terms_of_service: Option<String>,
49
50    /// Contact information of exposed API.
51    ///
52    /// See more details at: <https://spec.openapis.org/oas/latest.html#contact-object>.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub contact: Option<Contact>,
55
56    /// License of the API.
57    ///
58    /// See more details at: <https://spec.openapis.org/oas/latest.html#license-object>.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub license: Option<License>,
61
62    /// Document version typically the API version.
63    pub version: String,
64
65    /// Optional extensions "x-something"
66    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
67    pub extensions: PropMap<String, serde_json::Value>,
68}
69
70impl Info {
71    /// Construct a new [`Info`] object.
72    ///
73    /// Accepts two arguments: the API title, and the document version (typically the
74    /// API version).
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// # use salvo_oapi::Info;
80    /// let info = Info::new("Pet api", "1.1.0");
81    /// ```
82    #[must_use]
83    pub fn new(title: impl Into<String>, version: impl Into<String>) -> Self {
84        Self {
85            title: title.into(),
86            version: version.into(),
87            ..Default::default()
88        }
89    }
90    /// Set the title of the API.
91    #[must_use]
92    pub fn title<I: Into<String>>(mut self, title: I) -> Self {
93        self.title = title.into();
94        self
95    }
96
97    /// Set the version of the API document (typically the API version).
98    #[must_use]
99    pub fn version<I: Into<String>>(mut self, version: I) -> Self {
100        self.version = version.into();
101        self
102    }
103
104    /// Set the short summary of the API.
105    #[must_use]
106    pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
107        self.summary = Some(summary.into());
108        self
109    }
110
111    /// Set the description of the API.
112    #[must_use]
113    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
114        self.description = Some(description.into());
115        self
116    }
117
118    /// Set the URL pointing to the terms of service for the API.
119    #[must_use]
120    pub fn terms_of_service<S: Into<String>>(mut self, terms_of_service: S) -> Self {
121        self.terms_of_service = Some(terms_of_service.into());
122        self
123    }
124
125    /// Set the contact information of the API.
126    #[must_use]
127    pub fn contact(mut self, contact: Contact) -> Self {
128        self.contact = Some(contact);
129        self
130    }
131
132    /// Set the license of the API.
133    #[must_use]
134    pub fn license(mut self, license: License) -> Self {
135        self.license = Some(license);
136        self
137    }
138}
139
140/// OpenAPI [Contact][contact] information of the API.
141///
142/// You can use [`Contact::new`] to construct a new [`Contact`] object.
143///
144/// [contact]: <https://spec.openapis.org/oas/latest.html#contact-object>
145#[non_exhaustive]
146#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
147#[serde(rename_all = "camelCase")]
148pub struct Contact {
149    /// Identifying name of the contact person or organization of the API.
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub name: Option<String>,
152
153    /// Url pointing to contact information of the API.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub url: Option<String>,
156
157    /// Email of the contact person or the organization of the API.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub email: Option<String>,
160
161    /// Optional extensions "x-something"
162    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
163    pub extensions: PropMap<String, serde_json::Value>,
164}
165
166impl Contact {
167    /// Construct a new empty [`Contact`]. This is effectively same as calling [`Contact::default`].
168    #[must_use]
169    pub fn new() -> Self {
170        Default::default()
171    }
172    /// Add name contact person or organization of the API.
173    #[must_use]
174    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
175        self.name = Some(name.into());
176        self
177    }
178
179    /// Add url pointing to the contact information of the API.
180    #[must_use]
181    pub fn url<S: Into<String>>(mut self, url: S) -> Self {
182        self.url = Some(url.into());
183        self
184    }
185
186    /// Add email of the contact person or organization of the API.
187    #[must_use]
188    pub fn email<S: Into<String>>(mut self, email: S) -> Self {
189        self.email = Some(email.into());
190        self
191    }
192
193    /// Add openapi extensions (`x-something`) for [`Contact`].
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
201/// OpenAPI [License][license] information of the API.
202///
203/// [license]: <https://spec.openapis.org/oas/latest.html#license-object>
204#[non_exhaustive]
205#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, Debug)]
206#[serde(rename_all = "camelCase")]
207pub struct License {
208    /// Name of the license used e.g MIT or Apache-2.0
209    pub name: String,
210
211    /// Optional url pointing to the license.
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub url: Option<String>,
214
215    /// An [SPDX-Licenses][spdx_licence] expression for the API. The _`identifier`_ field
216    /// is mutually exclusive of the _`url`_ field. E.g. Apache-2.0
217    ///
218    /// [spdx_licence]: <https://spdx.org/licenses/>
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub identifier: Option<String>,
221
222    /// Optional extensions "x-something"
223    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
224    pub extensions: PropMap<String, serde_json::Value>,
225}
226
227impl License {
228    /// Construct a new [`License`] object.
229    ///
230    /// Function takes name of the license as an argument e.g MIT.
231    #[must_use]
232    pub fn new<S: Into<String>>(name: S) -> Self {
233        Self {
234            name: name.into(),
235            ..Default::default()
236        }
237    }
238    /// Add name of the license used in API.
239    #[must_use]
240    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
241        self.name = name.into();
242        self
243    }
244
245    /// Add url pointing to the license used in API.
246    #[must_use]
247    pub fn url<S: Into<String>>(mut self, url: S) -> Self {
248        self.url = Some(url.into());
249        self.identifier = None;
250        self
251    }
252
253    /// Set identifier of the licence as [SPDX-Licenses][spdx_licence] expression for the API.
254    /// The _`identifier`_ field is mutually exclusive of the _`url`_ field. E.g. Apache-2.0
255    ///
256    /// [spdx_licence]: <https://spdx.org/licenses/>
257    #[must_use]
258    pub fn identifier<S: Into<String>>(mut self, identifier: S) -> Self {
259        self.identifier = Some(identifier.into());
260        self.url = None;
261        self
262    }
263
264    /// Add openapi extensions (`x-something`) for [`License`].
265    #[must_use]
266    pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
267        self.extensions = extensions;
268        self
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use assert_json_diff::assert_json_eq;
275    use serde_json::json;
276
277    use super::Contact;
278    use crate::License;
279
280    #[test]
281    fn build_contact() {
282        let contact = Contact::new();
283
284        assert!(contact.name.is_none());
285        assert!(contact.url.is_none());
286        assert!(contact.email.is_none());
287
288        let contact = contact
289            .name("salvo api")
290            .url("https://github.com/salvo-rs/salvo")
291            .email("salvo.rs@some.mail.com");
292        assert_json_eq!(
293            contact,
294            json!({
295                "name": "salvo api",
296                "url": "https://github.com/salvo-rs/salvo",
297                "email": "salvo.rs@some.mail.com"
298            })
299        );
300    }
301
302    #[test]
303    fn test_license_set_name() {
304        let license = License::default();
305        assert!(license.name.is_empty());
306
307        let license = license.name("MIT");
308        assert_json_eq!(license, json!({ "name": "MIT" }));
309    }
310}