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