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
149impl Contact {
150    /// Construct a new empty [`Contact`]. This is effectively same as calling [`Contact::default`].
151    #[must_use]
152    pub fn new() -> Self {
153        Default::default()
154    }
155    /// Add name contact person or organization of the API.
156    #[must_use]
157    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
158        self.name = Some(name.into());
159        self
160    }
161
162    /// Add url pointing to the contact information of the API.
163    #[must_use]
164    pub fn url<S: Into<String>>(mut self, url: S) -> Self {
165        self.url = Some(url.into());
166        self
167    }
168
169    /// Add email of the contact person or organization of the API.
170    #[must_use]
171    pub fn email<S: Into<String>>(mut self, email: S) -> Self {
172        self.email = Some(email.into());
173        self
174    }
175}
176
177/// OpenAPI [License][license] information of the API.
178///
179/// [license]: <https://spec.openapis.org/oas/latest.html#license-object>
180#[non_exhaustive]
181#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, Debug)]
182#[serde(rename_all = "camelCase")]
183pub struct License {
184    /// Name of the license used e.g MIT or Apache-2.0
185    pub name: String,
186
187    /// Optional url pointing to the license.
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub url: Option<String>,
190}
191
192impl License {
193    /// Construct a new [`License`] object.
194    ///
195    /// Function takes name of the license as an argument e.g MIT.
196    #[must_use]
197    pub fn new<S: Into<String>>(name: S) -> Self {
198        Self {
199            name: name.into(),
200            ..Default::default()
201        }
202    }
203    /// Add name of the license used in API.
204    #[must_use]
205    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
206        self.name = name.into();
207        self
208    }
209
210    /// Add url pointing to the license used in API.
211    #[must_use]
212    pub fn url<S: Into<String>>(mut self, url: S) -> Self {
213        self.url = Some(url.into());
214        self
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use assert_json_diff::assert_json_eq;
221    use serde_json::json;
222
223    use super::Contact;
224    use crate::License;
225
226    #[test]
227    fn build_contact() {
228        let contact = Contact::new();
229
230        assert!(contact.name.is_none());
231        assert!(contact.url.is_none());
232        assert!(contact.email.is_none());
233
234        let contact = contact
235            .name("salvo api")
236            .url("https://github.com/salvo-rs/salvo")
237            .email("salvo.rs@some.mail.com");
238        assert_json_eq!(
239            contact,
240            json!({
241                "name": "salvo api",
242                "url": "https://github.com/salvo-rs/salvo",
243                "email": "salvo.rs@some.mail.com"
244            })
245        );
246    }
247
248    #[test]
249    fn test_license_set_name() {
250        let license = License::default();
251        assert!(license.name.is_empty());
252
253        let license = license.name("MIT");
254        assert_json_eq!(license, json!({ "name": "MIT" }));
255    }
256}