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    pub fn new(title: impl Into<String>, version: impl Into<String>) -> Self {
79        Self {
80            title: title.into(),
81            version: version.into(),
82            ..Default::default()
83        }
84    }
85    /// Add title of the API.
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    pub fn version<I: Into<String>>(mut self, version: I) -> Self {
93        self.version = version.into();
94        self
95    }
96
97    /// Add description of the API.
98    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
99        self.description = Some(description.into());
100        self
101    }
102
103    /// Add url for terms of the API.
104    pub fn terms_of_service<S: Into<String>>(mut self, terms_of_service: S) -> Self {
105        self.terms_of_service = Some(terms_of_service.into());
106        self
107    }
108
109    /// Add contact information of the API.
110    pub fn contact(mut self, contact: Contact) -> Self {
111        self.contact = Some(contact);
112        self
113    }
114
115    /// Add license of the API.
116    pub fn license(mut self, license: License) -> Self {
117        self.license = Some(license);
118        self
119    }
120}
121
122/// OpenAPI [Contact][contact] information of the API.
123///
124/// You can use [`Contact::new`] to construct a new [`Contact`] object.
125///
126/// [contact]: <https://spec.openapis.org/oas/latest.html#contact-object>
127#[non_exhaustive]
128#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
129#[serde(rename_all = "camelCase")]
130pub struct Contact {
131    /// Identifying name of the contact person or organization of the API.
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub name: Option<String>,
134
135    /// Url pointing to contact information of the API.
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub url: Option<String>,
138
139    /// Email of the contact person or the organization of the API.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub email: Option<String>,
142}
143
144impl Contact {
145    /// Construct a new empty [`Contact`]. This is effectively same as calling [`Contact::default`].
146    pub fn new() -> Self {
147        Default::default()
148    }
149    /// Add name contact person or organization of the API.
150    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
151        self.name = Some(name.into());
152        self
153    }
154
155    /// Add url pointing to the contact information of the API.
156    pub fn url<S: Into<String>>(mut self, url: S) -> Self {
157        self.url = Some(url.into());
158        self
159    }
160
161    /// Add email of the contact person or organization of the API.
162    pub fn email<S: Into<String>>(mut self, email: S) -> Self {
163        self.email = Some(email.into());
164        self
165    }
166}
167
168/// OpenAPI [License][license] information of the API.
169///
170/// [license]: <https://spec.openapis.org/oas/latest.html#license-object>
171#[non_exhaustive]
172#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, Debug)]
173#[serde(rename_all = "camelCase")]
174pub struct License {
175    /// Name of the license used e.g MIT or Apache-2.0
176    pub name: String,
177
178    /// Optional url pointing to the license.
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub url: Option<String>,
181}
182
183impl License {
184    /// Construct a new [`License`] object.
185    ///
186    /// Function takes name of the license as an argument e.g MIT.
187    pub fn new<S: Into<String>>(name: S) -> Self {
188        Self {
189            name: name.into(),
190            ..Default::default()
191        }
192    }
193    /// Add name of the license used in API.
194    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
195        self.name = name.into();
196        self
197    }
198
199    /// Add url pointing to the license used in API.
200    pub fn url<S: Into<String>>(mut self, url: S) -> Self {
201        self.url = Some(url.into());
202        self
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use assert_json_diff::assert_json_eq;
209    use serde_json::json;
210
211    use crate::License;
212
213    use super::Contact;
214
215    #[test]
216    fn build_contact() {
217        let contact = Contact::new();
218
219        assert!(contact.name.is_none());
220        assert!(contact.url.is_none());
221        assert!(contact.email.is_none());
222
223        let contact = contact
224            .name("salvo api")
225            .url("https://github.com/salvo-rs/salvo")
226            .email("salvo.rs@some.mail.com");
227        assert_json_eq!(
228            contact,
229            json!({
230                "name": "salvo api",
231                "url": "https://github.com/salvo-rs/salvo",
232                "email": "salvo.rs@some.mail.com"
233            })
234        );
235    }
236
237    #[test]
238    fn test_license_set_name() {
239        let license = License::default();
240        assert!(license.name.is_empty());
241
242        let license = license.name("MIT");
243        assert_json_eq!(license, json!({ "name": "MIT" }));
244    }
245}