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