utoipa/
openapi.rs

1//! Rust implementation of Openapi Spec V3.
2
3use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
4use std::fmt::Formatter;
5
6pub use self::{
7    content::{Content, ContentBuilder},
8    external_docs::ExternalDocs,
9    header::{Header, HeaderBuilder},
10    info::{Contact, ContactBuilder, Info, InfoBuilder, License, LicenseBuilder},
11    path::{PathItem, PathItemType, Paths, PathsBuilder},
12    response::{Response, ResponseBuilder, Responses, ResponsesBuilder},
13    schema::{
14        AllOf, AllOfBuilder, Array, ArrayBuilder, Components, ComponentsBuilder, Discriminator,
15        KnownFormat, Object, ObjectBuilder, OneOf, OneOfBuilder, Ref, Schema, SchemaFormat,
16        SchemaType, ToArray,
17    },
18    security::SecurityRequirement,
19    server::{Server, ServerBuilder, ServerVariable, ServerVariableBuilder},
20    tag::Tag,
21};
22
23pub mod content;
24pub mod encoding;
25pub mod example;
26pub mod external_docs;
27pub mod header;
28pub mod info;
29pub mod path;
30pub mod request_body;
31pub mod response;
32pub mod schema;
33pub mod security;
34pub mod server;
35pub mod tag;
36pub mod xml;
37
38builder! {
39    /// # Examples
40    ///
41    /// Create [`OpenApi`] using [`OpenApiBuilder`].
42    /// ```rust
43    /// # use utoipa::openapi::{Info, Paths, Components, OpenApiBuilder};
44    /// let openapi = OpenApiBuilder::new()
45    ///      .info(Info::new("My api", "1.0.0"))
46    ///      .paths(Paths::new())
47    ///      .components(Some(
48    ///          Components::new()
49    ///      ))
50    ///      .build();
51    /// ```
52    OpenApiBuilder;
53
54    /// Root object of the OpenAPI document.
55    ///
56    /// You can use [`OpenApi::new`] function to construct a new [`OpenApi`] instance and then
57    /// use the fields with mutable access to modify them. This is quite tedious if you are not simply
58    /// just changing one thing thus you can also use the [`OpenApiBuilder::new`] to use builder to
59    /// construct a new [`OpenApi`] object.
60    ///
61    /// See more details at <https://spec.openapis.org/oas/latest.html#openapi-object>.
62    #[non_exhaustive]
63    #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
64    #[cfg_attr(feature = "debug", derive(Debug))]
65    #[serde(rename_all = "camelCase")]
66    pub struct OpenApi {
67        /// OpenAPI document version.
68        pub openapi: OpenApiVersion,
69
70        /// Provides metadata about the API.
71        ///
72        /// See more details at <https://spec.openapis.org/oas/latest.html#info-object>.
73        pub info: Info,
74
75        /// Optional list of servers that provides the connectivity information to target servers.
76        ///
77        /// This is implicitly one server with `url` set to `/`.
78        ///
79        /// See more details at <https://spec.openapis.org/oas/latest.html#server-object>.
80        #[serde(skip_serializing_if = "Option::is_none")]
81        pub servers: Option<Vec<Server>>,
82
83        /// Available paths and operations for the API.
84        ///
85        /// See more details at <https://spec.openapis.org/oas/latest.html#paths-object>.
86        #[serde(flatten)]
87        pub paths: Paths,
88
89        /// Holds various reusable schemas for the OpenAPI document.
90        ///
91        /// Few of these elements are security schemas and object schemas.
92        ///
93        /// See more details at <https://spec.openapis.org/oas/latest.html#components-object>.
94        #[serde(skip_serializing_if = "Option::is_none")]
95        pub components: Option<Components>,
96
97        /// Declaration of global security mechanisms that can be used across the API. The individual operations
98        /// can override the declarations. You can use `SecurityRequirement::default()` if you wish to make security
99        /// optional by adding it to the list of securities.
100        ///
101        /// See more details at <https://spec.openapis.org/oas/latest.html#security-requirement-object>.
102        #[serde(skip_serializing_if = "Option::is_none")]
103        pub security: Option<Vec<SecurityRequirement>>,
104
105        /// Optional list of tags can be used to add additional documentation to matching tags of operations.
106        ///
107        /// See more details at <https://spec.openapis.org/oas/latest.html#tag-object>.
108        #[serde(skip_serializing_if = "Option::is_none")]
109        pub tags: Option<Vec<Tag>>,
110
111        /// Optional global additional documentation reference.
112        ///
113        /// See more details at <https://spec.openapis.org/oas/latest.html#external-documentation-object>.
114        #[serde(skip_serializing_if = "Option::is_none")]
115        pub external_docs: Option<ExternalDocs>,
116    }
117}
118
119impl OpenApi {
120    /// Construct a new [`OpenApi`] object.
121    ///
122    /// Function accepts two arguments one which is [`Info`] metadata of the API; two which is [`Paths`]
123    /// containing operations for the API.
124    ///
125    /// # Examples
126    ///
127    /// ```rust
128    /// # use utoipa::openapi::{Info, Paths, OpenApi};
129    /// #
130    /// let openapi = OpenApi::new(Info::new("pet api", "0.1.0"), Paths::new());
131    /// ```
132    pub fn new<P: Into<Paths>>(info: Info, paths: P) -> Self {
133        Self {
134            info,
135            paths: paths.into(),
136            ..Default::default()
137        }
138    }
139
140    /// Converts this [`OpenApi`] to JSON String. This method essentially calls [`serde_json::to_string`] method.
141    pub fn to_json(&self) -> Result<String, serde_json::Error> {
142        serde_json::to_string(self)
143    }
144
145    /// Converts this [`OpenApi`] to pretty JSON String. This method essentially calls [`serde_json::to_string_pretty`] method.
146    pub fn to_pretty_json(&self) -> Result<String, serde_json::Error> {
147        serde_json::to_string_pretty(self)
148    }
149
150    /// Converts this [`OpenApi`] to YAML String. This method essentially calls [`serde_yaml::to_string`] method.
151    #[cfg(feature = "yaml")]
152    #[cfg_attr(doc_cfg, doc(cfg(feature = "yaml")))]
153    pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
154        serde_yaml::to_string(self)
155    }
156
157    /// Merge `other` [`OpenApi`] consuming it and resuming it's content.
158    ///
159    /// Merge function will take all `self` nonexistent _`servers`, `paths`, `schemas`, `responses`,
160    /// `security_schemes`, `security_requirements` and `tags`_ from _`other`_ [`OpenApi`].
161    ///
162    /// This function performs a shallow comparison for `paths`, `schemas`, `responses` and
163    /// `security schemes` which means that only _`name`_ and _`path`_ is used for comparison. When
164    /// match occurs the whole item will be ignored from merged results. Only items not
165    /// found will be appended to `self`.
166    ///
167    /// For _`servers`_, _`tags`_ and _`security_requirements`_ the whole item will be used for
168    /// comparison. Items not found from `self` will be appended to `self`.
169    ///
170    /// **Note!** `info`, `openapi` and `external_docs` will not be merged.
171    pub fn merge(&mut self, mut other: OpenApi) {
172        if let Some(other_servers) = &mut other.servers {
173            let servers = self.servers.get_or_insert(Vec::new());
174            other_servers.retain(|server| !servers.contains(server));
175            servers.append(other_servers);
176        }
177
178        if !other.paths.paths.is_empty() {
179            other
180                .paths
181                .paths
182                .retain(|path, _| self.paths.get_path_item(path).is_none());
183            self.paths.paths.extend(&mut other.paths.paths.into_iter());
184        };
185
186        if let Some(other_components) = &mut other.components {
187            let components = self.components.get_or_insert(Components::default());
188
189            other_components
190                .schemas
191                .retain(|name, _| !components.schemas.contains_key(name));
192            components.schemas.append(&mut other_components.schemas);
193
194            other_components
195                .responses
196                .retain(|name, _| !components.responses.contains_key(name));
197            components.responses.append(&mut other_components.responses);
198
199            other_components
200                .security_schemes
201                .retain(|name, _| !components.security_schemes.contains_key(name));
202            components
203                .security_schemes
204                .append(&mut other_components.security_schemes);
205        }
206
207        if let Some(other_security) = &mut other.security {
208            let security = self.security.get_or_insert(Vec::new());
209            other_security.retain(|requirement| !security.contains(requirement));
210            security.append(other_security);
211        }
212
213        if let Some(other_tags) = &mut other.tags {
214            let tags = self.tags.get_or_insert(Vec::new());
215            other_tags.retain(|tag| !tags.contains(tag));
216            tags.append(other_tags);
217        }
218    }
219}
220
221impl OpenApiBuilder {
222    /// Add [`Info`] metadata of the API.
223    pub fn info<I: Into<Info>>(mut self, info: I) -> Self {
224        set_value!(self info info.into())
225    }
226
227    /// Add iterator of [`Server`]s to configure target servers.
228    pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: Option<I>) -> Self {
229        set_value!(self servers servers.map(|servers| servers.into_iter().collect()))
230    }
231
232    /// Add [`Paths`] to configure operations and endpoints of the API.
233    pub fn paths<P: Into<Paths>>(mut self, paths: P) -> Self {
234        set_value!(self paths paths.into())
235    }
236
237    /// Add [`Components`] to configure reusable schemas.
238    pub fn components(mut self, components: Option<Components>) -> Self {
239        set_value!(self components components)
240    }
241
242    /// Add iterator of [`SecurityRequirement`]s that are globally available for all operations.
243    pub fn security<I: IntoIterator<Item = SecurityRequirement>>(
244        mut self,
245        security: Option<I>,
246    ) -> Self {
247        set_value!(self security security.map(|security| security.into_iter().collect()))
248    }
249
250    /// Add iterator of [`Tag`]s to add additional documentation for **operations** tags.
251    pub fn tags<I: IntoIterator<Item = Tag>>(mut self, tags: Option<I>) -> Self {
252        set_value!(self tags tags.map(|tags| tags.into_iter().collect()))
253    }
254
255    /// Add [`ExternalDocs`] for referring additional documentation.
256    pub fn external_docs(mut self, external_docs: Option<ExternalDocs>) -> Self {
257        set_value!(self external_docs external_docs)
258    }
259}
260
261/// Represents available [OpenAPI versions][version].
262///
263/// [version]: <https://spec.openapis.org/oas/latest.html#versions>
264#[derive(Serialize, Clone, PartialEq, Eq)]
265#[cfg_attr(feature = "debug", derive(Debug))]
266pub enum OpenApiVersion {
267    /// Will serialize to `3.0.3` the latest from 3.0 serde.
268    #[serde(rename = "3.0.3")]
269    Version3,
270}
271
272impl Default for OpenApiVersion {
273    fn default() -> Self {
274        Self::Version3
275    }
276}
277
278impl<'de> Deserialize<'de> for OpenApiVersion {
279    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280    where
281        D: Deserializer<'de>,
282    {
283        struct VersionVisitor;
284
285        impl<'v> Visitor<'v> for VersionVisitor {
286            type Value = OpenApiVersion;
287
288            fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
289                formatter.write_str("a version string in 3, 3.0, or 3.0.x format")
290            }
291
292            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
293            where
294                E: Error,
295            {
296                self.visit_string(v.to_string())
297            }
298
299            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
300            where
301                E: Error,
302            {
303                let parts = v.split('.').collect::<Vec<_>>();
304                if parts.len() > 3 || parts.is_empty() {
305                    return Err(E::custom(format!(
306                        "Invalid format of OpenAPI version: {}",
307                        v,
308                    )));
309                }
310
311                Ok(match (parts[0], parts.get(1).copied().unwrap_or("0")) {
312                    ("3", "0") => OpenApiVersion::Version3,
313                    _ => return Err(E::custom(format!("Unsupported version: {}", &v))),
314                })
315            }
316        }
317
318        deserializer.deserialize_string(VersionVisitor)
319    }
320}
321
322/// Value used to indicate whether reusable schema, parameter or operation is deprecated.
323///
324/// The value will serialize to boolean.
325#[derive(PartialEq, Eq, Clone)]
326#[cfg_attr(feature = "debug", derive(Debug))]
327pub enum Deprecated {
328    True,
329    False,
330}
331
332impl Serialize for Deprecated {
333    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
334    where
335        S: Serializer,
336    {
337        serializer.serialize_bool(matches!(self, Self::True))
338    }
339}
340
341impl<'de> Deserialize<'de> for Deprecated {
342    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
343    where
344        D: serde::Deserializer<'de>,
345    {
346        struct BoolVisitor;
347        impl<'de> Visitor<'de> for BoolVisitor {
348            type Value = Deprecated;
349
350            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
351                formatter.write_str("a bool true or false")
352            }
353
354            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
355            where
356                E: serde::de::Error,
357            {
358                match v {
359                    true => Ok(Deprecated::True),
360                    false => Ok(Deprecated::False),
361                }
362            }
363        }
364        deserializer.deserialize_bool(BoolVisitor)
365    }
366}
367
368impl Default for Deprecated {
369    fn default() -> Self {
370        Deprecated::False
371    }
372}
373
374/// Value used to indicate whether parameter or property is required.
375///
376/// The value will serialize to boolean.
377#[derive(PartialEq, Eq, Clone)]
378#[cfg_attr(feature = "debug", derive(Debug))]
379pub enum Required {
380    True,
381    False,
382}
383
384impl Serialize for Required {
385    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
386    where
387        S: Serializer,
388    {
389        serializer.serialize_bool(matches!(self, Self::True))
390    }
391}
392
393impl<'de> Deserialize<'de> for Required {
394    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
395    where
396        D: serde::Deserializer<'de>,
397    {
398        struct BoolVisitor;
399        impl<'de> Visitor<'de> for BoolVisitor {
400            type Value = Required;
401
402            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
403                formatter.write_str("a bool true or false")
404            }
405
406            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
407            where
408                E: serde::de::Error,
409            {
410                match v {
411                    true => Ok(Required::True),
412                    false => Ok(Required::False),
413                }
414            }
415        }
416        deserializer.deserialize_bool(BoolVisitor)
417    }
418}
419
420impl Default for Required {
421    fn default() -> Self {
422        Required::False
423    }
424}
425
426/// A [`Ref`] or some other type `T`.
427///
428/// Typically used in combination with [`Components`] and is an union type between [`Ref`] and any
429/// other given type such as [`Schema`] or [`Response`].
430#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
431#[cfg_attr(feature = "debug", derive(Debug))]
432#[serde(untagged)]
433pub enum RefOr<T> {
434    Ref(Ref),
435    T(T),
436}
437
438macro_rules! build_fn {
439    ( $vis:vis $name:ident $( $field:ident ),+ ) => {
440        #[doc = concat!("Constructs a new [`", stringify!($name),"`] taking all fields values from this object.")]
441        $vis fn build(self) -> $name {
442            $name {
443                $(
444                    $field: self.$field,
445                )*
446            }
447        }
448    };
449}
450pub(crate) use build_fn;
451
452macro_rules! set_value {
453    ( $self:ident $field:ident $value:expr ) => {{
454        $self.$field = $value;
455
456        $self
457    }};
458}
459pub(crate) use set_value;
460
461macro_rules! new {
462    ( $vis:vis $name:ident ) => {
463        #[doc = concat!("Constructs a new [`", stringify!($name),"`].")]
464        $vis fn new() -> $name {
465            $name {
466                ..Default::default()
467            }
468        }
469    };
470}
471pub(crate) use new;
472
473macro_rules! from {
474    ( $name:ident $to:ident $( $field:ident ),+ ) => {
475        impl From<$name> for $to {
476            fn from(value: $name) -> Self {
477                Self {
478                    $( $field: value.$field, )*
479                }
480            }
481        }
482
483        impl From<$to> for $name {
484            fn from(value: $to) -> Self {
485                value.build()
486            }
487        }
488    };
489}
490pub(crate) use from;
491
492macro_rules! builder {
493    ( $( #[$builder_meta:meta] )* $builder_name:ident; $(#[$meta:meta])* $vis:vis $key:ident $name:ident $( $tt:tt )* ) => {
494        builder!( @type_impl $( #[$meta] )* $vis $key $name $( $tt )* );
495        builder!( @builder_impl $( #[$builder_meta] )* $builder_name $( #[$meta] )* $vis $key $name $( $tt )* );
496    };
497
498    ( @type_impl $( #[$meta:meta] )* $vis:vis $key:ident $name:ident
499        { $( $( #[$field_meta:meta] )* $field_vis:vis $field:ident: $field_ty:ty, )* }
500    ) => {
501
502        $( #[$meta] )*
503        $vis $key $name {
504            $( $( #[$field_meta] )* $field_vis $field: $field_ty, )*
505        }
506    };
507
508    ( @builder_impl $( #[$builder_meta:meta] )* $builder_name:ident $( #[$meta:meta] )* $vis:vis $key:ident $name:ident
509        { $( $( #[$field_meta:meta] )* $field_vis:vis $field:ident: $field_ty:ty, )* }
510    ) => {
511        #[doc = concat!("Builder for [`", stringify!($name),
512            "`] with chainable configuration methods to create a new [`", stringify!($name) , "`].")]
513        $( #[$builder_meta] )*
514        #[cfg_attr(feature = "debug", derive(Debug))]
515        $vis $key $builder_name {
516            $( $field: $field_ty, )*
517        }
518
519        impl Default for $builder_name {
520            fn default() -> Self {
521                let meta_default: $name = $name::default();
522                Self {
523                    $( $field: meta_default.$field, )*
524                }
525            }
526        }
527
528        impl $builder_name {
529            crate::openapi::new!($vis $builder_name);
530            crate::openapi::build_fn!($vis $name $( $field ),* );
531        }
532
533        crate::openapi::from!($name $builder_name $( $field ),* );
534    };
535}
536pub(crate) use builder;
537
538#[cfg(test)]
539mod tests {
540    use serde_json::json;
541
542    use crate::openapi::{
543        info::InfoBuilder,
544        path::{OperationBuilder, PathsBuilder},
545    };
546
547    use super::{response::Response, *};
548
549    #[test]
550    fn serialize_deserialize_openapi_version_success() -> Result<(), serde_json::Error> {
551        assert_eq!(serde_json::to_value(&OpenApiVersion::Version3)?, "3.0.3");
552        Ok(())
553    }
554
555    #[test]
556    fn serialize_openapi_json_minimal_success() -> Result<(), serde_json::Error> {
557        let raw_json = include_str!("openapi/testdata/expected_openapi_minimal.json");
558        let openapi = OpenApi::new(
559            InfoBuilder::new()
560                .title("My api")
561                .version("1.0.0")
562                .description(Some("My api description"))
563                .license(Some(
564                    LicenseBuilder::new()
565                        .name("MIT")
566                        .url(Some("http://mit.licence"))
567                        .build(),
568                ))
569                .build(),
570            Paths::new(),
571        );
572        let serialized = serde_json::to_string_pretty(&openapi)?;
573
574        assert_eq!(
575            serialized, raw_json,
576            "expected serialized json to match raw: \nserialized: \n{serialized} \nraw: \n{raw_json}"
577        );
578        Ok(())
579    }
580
581    #[test]
582    fn serialize_openapi_json_with_paths_success() -> Result<(), serde_json::Error> {
583        let openapi = OpenApi::new(
584            Info::new("My big api", "1.1.0"),
585            PathsBuilder::new()
586                .path(
587                    "/api/v1/users",
588                    PathItem::new(
589                        PathItemType::Get,
590                        OperationBuilder::new().response("200", Response::new("Get users list")),
591                    ),
592                )
593                .path(
594                    "/api/v1/users",
595                    PathItem::new(
596                        PathItemType::Post,
597                        OperationBuilder::new().response("200", Response::new("Post new user")),
598                    ),
599                )
600                .path(
601                    "/api/v1/users/{id}",
602                    PathItem::new(
603                        PathItemType::Get,
604                        OperationBuilder::new().response("200", Response::new("Get user by id")),
605                    ),
606                ),
607        );
608
609        let serialized = serde_json::to_string_pretty(&openapi)?;
610        let expected = include_str!("./openapi/testdata/expected_openapi_with_paths.json");
611
612        assert_eq!(
613            serialized, expected,
614            "expected serialized json to match raw: \nserialized: \n{serialized} \nraw: \n{expected}"
615        );
616        Ok(())
617    }
618
619    #[test]
620    fn merge_2_openapi_documents() {
621        let mut api_1 = OpenApi::new(
622            Info::new("Api", "v1"),
623            PathsBuilder::new()
624                .path(
625                    "/api/v1/user",
626                    PathItem::new(
627                        PathItemType::Get,
628                        OperationBuilder::new().response("200", Response::new("Get user success")),
629                    ),
630                )
631                .build(),
632        );
633
634        let api_2 = OpenApiBuilder::new()
635            .info(Info::new("Api", "v2"))
636            .paths(
637                PathsBuilder::new()
638                    .path(
639                        "/api/v1/user",
640                        PathItem::new(
641                            PathItemType::Get,
642                            OperationBuilder::new()
643                                .response("200", Response::new("This will not get added")),
644                        ),
645                    )
646                    .path(
647                        "/ap/v2/user",
648                        PathItem::new(
649                            PathItemType::Get,
650                            OperationBuilder::new()
651                                .response("200", Response::new("Get user success 2")),
652                        ),
653                    )
654                    .path(
655                        "/api/v2/user",
656                        PathItem::new(
657                            PathItemType::Post,
658                            OperationBuilder::new()
659                                .response("200", Response::new("Get user success")),
660                        ),
661                    )
662                    .build(),
663            )
664            .components(Some(
665                ComponentsBuilder::new()
666                    .schema(
667                        "User2",
668                        ObjectBuilder::new()
669                            .schema_type(SchemaType::Object)
670                            .property(
671                                "name",
672                                ObjectBuilder::new().schema_type(SchemaType::String).build(),
673                            ),
674                    )
675                    .build(),
676            ))
677            .build();
678
679        api_1.merge(api_2);
680        let value = serde_json::to_value(&api_1).unwrap();
681
682        assert_eq!(
683            value,
684            json!(
685                {
686                  "openapi": "3.0.3",
687                  "info": {
688                    "title": "Api",
689                    "version": "v1"
690                  },
691                  "paths": {
692                    "/ap/v2/user": {
693                      "get": {
694                        "responses": {
695                          "200": {
696                            "description": "Get user success 2"
697                          }
698                        }
699                      }
700                    },
701                    "/api/v1/user": {
702                      "get": {
703                        "responses": {
704                          "200": {
705                            "description": "Get user success"
706                          }
707                        }
708                      }
709                    },
710                    "/api/v2/user": {
711                      "post": {
712                        "responses": {
713                          "200": {
714                            "description": "Get user success"
715                          }
716                        }
717                      }
718                    }
719                  },
720                  "components": {
721                    "schemas": {
722                      "User2": {
723                        "type": "object",
724                        "properties": {
725                          "name": {
726                            "type": "string"
727                          }
728                        }
729                      }
730                    }
731                  }
732                }
733            )
734        )
735    }
736
737    #[test]
738    fn deserialize_other_versions() {
739        [r#""3.0.3""#, r#""3.0.0""#, r#""3.0""#, r#""3""#]
740            .iter()
741            .for_each(|v| {
742                assert!(matches!(
743                    serde_json::from_str::<OpenApiVersion>(v).unwrap(),
744                    OpenApiVersion::Version3,
745                ));
746            });
747    }
748}