utoipa/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 super::{builder, extensions::Extensions, set_value};
13
14builder! {
15    /// # Examples
16    ///
17    /// Create [`Info`] using [`InfoBuilder`].
18    /// ```rust
19    /// # use utoipa::openapi::{Info, InfoBuilder, ContactBuilder};
20    /// let info = InfoBuilder::new()
21    ///      .title("My api")
22    ///      .version("1.0.0")
23    ///      .contact(Some(ContactBuilder::new()
24    ///           .name(Some("Admin Admin"))
25    ///           .email(Some("amdin@petapi.com"))
26    ///           .build()
27    ///       ))
28    ///      .build();
29    /// ```
30    InfoBuilder;
31
32    /// OpenAPI [Info][info] object represents metadata of the API.
33    ///
34    /// You can use [`Info::new`] to construct a new [`Info`] object or alternatively use [`InfoBuilder::new`]
35    /// to construct a new [`Info`] with chainable configuration methods.
36    ///
37    /// [info]: <https://spec.openapis.org/oas/latest.html#info-object>
38    #[non_exhaustive]
39    #[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
40    #[cfg_attr(feature = "debug", derive(Debug))]
41    #[serde(rename_all = "camelCase")]
42    pub struct Info {
43        /// Title of the API.
44        pub title: String,
45
46        /// Optional description of the API.
47        ///
48        /// Value supports markdown syntax.
49        #[serde(skip_serializing_if = "Option::is_none")]
50        pub description: Option<String>,
51
52        /// Optional url for terms of service.
53        #[serde(skip_serializing_if = "Option::is_none")]
54        pub terms_of_service: Option<String>,
55
56        /// Contact information of exposed API.
57        ///
58        /// See more details at: <https://spec.openapis.org/oas/latest.html#contact-object>.
59        #[serde(skip_serializing_if = "Option::is_none")]
60        pub contact: Option<Contact>,
61
62        /// License of the API.
63        ///
64        /// See more details at: <https://spec.openapis.org/oas/latest.html#license-object>.
65        #[serde(skip_serializing_if = "Option::is_none")]
66        pub license: Option<License>,
67
68        /// Document version typically the API version.
69        pub version: String,
70
71        /// Optional extensions "x-something".
72        #[serde(skip_serializing_if = "Option::is_none", flatten)]
73        pub extensions: Option<Extensions>,
74    }
75}
76
77impl Info {
78    /// Construct a new [`Info`] object.
79    ///
80    /// This function accepts two arguments. One which is the title of the API and two the
81    /// version of the api document typically the API version.
82    ///
83    /// # Examples
84    ///
85    /// ```rust
86    /// # use utoipa::openapi::Info;
87    /// let info = Info::new("Pet api", "1.1.0");
88    /// ```
89    pub fn new<S: Into<String>>(title: S, version: S) -> Self {
90        Self {
91            title: title.into(),
92            version: version.into(),
93            ..Default::default()
94        }
95    }
96}
97
98impl InfoBuilder {
99    /// Add title of the API.
100    pub fn title<I: Into<String>>(mut self, title: I) -> Self {
101        set_value!(self title title.into())
102    }
103
104    /// Add version of the api document typically the API version.
105    pub fn version<I: Into<String>>(mut self, version: I) -> Self {
106        set_value!(self version version.into())
107    }
108
109    /// Add description of the API.
110    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
111        set_value!(self description description.map(|description| description.into()))
112    }
113
114    /// Add url for terms of the API.
115    pub fn terms_of_service<S: Into<String>>(mut self, terms_of_service: Option<S>) -> Self {
116        set_value!(self terms_of_service terms_of_service.map(|terms_of_service| terms_of_service.into()))
117    }
118
119    /// Add contact information of the API.
120    pub fn contact(mut self, contact: Option<Contact>) -> Self {
121        set_value!(self contact contact)
122    }
123
124    /// Add license of the API.
125    pub fn license(mut self, license: Option<License>) -> Self {
126        set_value!(self license license)
127    }
128
129    /// Add openapi extensions (x-something) of the API.
130    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
131        set_value!(self extensions extensions)
132    }
133}
134
135builder! {
136    /// See the [`InfoBuilder`] for combined usage example.
137    ContactBuilder;
138
139    /// OpenAPI [Contact][contact] information of the API.
140    ///
141    /// You can use [`Contact::new`] to construct a new [`Contact`] object or alternatively
142    /// use [`ContactBuilder::new`] to construct a new [`Contact`] with chainable configuration methods.
143    ///
144    /// [contact]: <https://spec.openapis.org/oas/latest.html#contact-object>
145    #[non_exhaustive]
146    #[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
147    #[cfg_attr(feature = "debug", derive(Debug))]
148    #[serde(rename_all = "camelCase")]
149    pub struct Contact {
150        /// Identifying name of the contact person or organization of the API.
151        #[serde(skip_serializing_if = "Option::is_none")]
152        pub name: Option<String>,
153
154        /// Url pointing to contact information of the API.
155        #[serde(skip_serializing_if = "Option::is_none")]
156        pub url: Option<String>,
157
158        /// Email of the contact person or the organization of the API.
159        #[serde(skip_serializing_if = "Option::is_none")]
160        pub email: Option<String>,
161
162        /// Optional extensions "x-something".
163        #[serde(skip_serializing_if = "Option::is_none", flatten)]
164        pub extensions: Option<Extensions>,
165    }
166}
167
168impl Contact {
169    /// Construct a new [`Contact`].
170    pub fn new() -> Self {
171        Default::default()
172    }
173}
174
175impl ContactBuilder {
176    /// Add name contact person or organization of the API.
177    pub fn name<S: Into<String>>(mut self, name: Option<S>) -> Self {
178        set_value!(self name name.map(|name| name.into()))
179    }
180
181    /// Add url pointing to the contact information of the API.
182    pub fn url<S: Into<String>>(mut self, url: Option<S>) -> Self {
183        set_value!(self url url.map(|url| url.into()))
184    }
185
186    /// Add email of the contact person or organization of the API.
187    pub fn email<S: Into<String>>(mut self, email: Option<S>) -> Self {
188        set_value!(self email email.map(|email| email.into()))
189    }
190
191    /// Add openapi extensions (x-something) of the API.
192    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
193        set_value!(self extensions extensions)
194    }
195}
196
197builder! {
198    LicenseBuilder;
199
200    /// OpenAPI [License][license] information of the API.
201    ///
202    /// [license]: <https://spec.openapis.org/oas/latest.html#license-object>
203    #[non_exhaustive]
204    #[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
205    #[cfg_attr(feature = "debug", derive(Debug))]
206    #[serde(rename_all = "camelCase")]
207    pub 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 = "Option::is_none", flatten)]
224        pub extensions: Option<Extensions>,
225    }
226}
227
228impl License {
229    /// Construct a new [`License`] object.
230    ///
231    /// Function takes name of the license as an argument e.g MIT.
232    pub fn new<S: Into<String>>(name: S) -> Self {
233        Self {
234            name: name.into(),
235            ..Default::default()
236        }
237    }
238}
239
240impl LicenseBuilder {
241    /// Add name of the license used in API.
242    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
243        set_value!(self name name.into())
244    }
245
246    /// Add url pointing to the license used in API.
247    pub fn url<S: Into<String>>(mut self, url: Option<S>) -> Self {
248        set_value!(self url url.map(|url| url.into()))
249    }
250
251    /// Add openapi extensions (x-something) of the API.
252    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
253        set_value!(self extensions extensions)
254    }
255
256    /// Set identifier of the licence as [SPDX-Licenses][spdx_licence] expression for the API.
257    /// The _`identifier`_ field is mutually exclusive of the _`url`_ field. E.g. Apache-2.0
258    ///
259    /// [spdx_licence]: <https://spdx.org/licenses/>
260    pub fn identifier<S: Into<String>>(mut self, identifier: Option<S>) -> Self {
261        set_value!(self identifier identifier.map(|identifier| identifier.into()))
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::Contact;
268
269    #[test]
270    fn contact_new() {
271        let contact = Contact::new();
272
273        assert!(contact.name.is_none());
274        assert!(contact.url.is_none());
275        assert!(contact.email.is_none());
276    }
277}