Skip to main content

utoipa/openapi/
security.rs

1//! Implements [OpenAPI Security Schema][security] types.
2//!
3//! Refer to [`SecurityScheme`] for usage and more details.
4//!
5//! [security]: https://spec.openapis.org/oas/latest.html#security-scheme-object
6use std::{collections::BTreeMap, iter};
7
8use serde::{Deserialize, Serialize};
9
10use super::{builder, extensions::Extensions, Deprecated};
11
12/// OpenAPI [security requirement][security] object.
13///
14/// Security requirement holds list of required [`SecurityScheme`] *names* and possible *scopes* required
15/// to execute the operation. They can be defined in [`#[utoipa::path(...)]`][path] or in `#[openapi(...)]`
16/// of [`OpenApi`][openapi].
17///
18/// Applying the security requirement to [`OpenApi`][openapi] will make it globally
19/// available to all operations. When applied to specific [`#[utoipa::path(...)]`][path] will only
20/// make the security requirements available for that operation. Only one of the requirements must be
21/// satisfied.
22///
23/// [security]: https://spec.openapis.org/oas/latest.html#security-requirement-object
24/// [path]: ../../attr.path.html
25/// [openapi]: ../../derive.OpenApi.html
26#[non_exhaustive]
27#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
28#[cfg_attr(feature = "debug", derive(Debug))]
29pub struct SecurityRequirement {
30    #[serde(flatten)]
31    value: BTreeMap<String, Vec<String>>,
32}
33
34impl SecurityRequirement {
35    /// Construct a new [`SecurityRequirement`].
36    ///
37    /// Accepts name for the security requirement which must match to the name of available [`SecurityScheme`].
38    /// Second parameter is [`IntoIterator`] of [`Into<String>`] scopes needed by the [`SecurityRequirement`].
39    /// Scopes must match to the ones defined in [`SecurityScheme`].
40    ///
41    /// # Examples
42    ///
43    /// Create new security requirement with scopes.
44    /// ```rust
45    /// # use utoipa::openapi::security::SecurityRequirement;
46    /// SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
47    /// ```
48    ///
49    /// You can also create an empty security requirement with `Default::default()`.
50    /// ```rust
51    /// # use utoipa::openapi::security::SecurityRequirement;
52    /// SecurityRequirement::default();
53    /// ```
54    ///
55    /// If you have more than one name in the security requirement you can use
56    /// [`SecurityRequirement::add`].
57    pub fn new<N: Into<String>, S: IntoIterator<Item = I>, I: Into<String>>(
58        name: N,
59        scopes: S,
60    ) -> Self {
61        Self {
62            value: BTreeMap::from_iter(iter::once_with(|| {
63                (
64                    Into::<String>::into(name),
65                    scopes
66                        .into_iter()
67                        .map(|scope| Into::<String>::into(scope))
68                        .collect::<Vec<_>>(),
69                )
70            })),
71        }
72    }
73
74    /// Allows to add multiple names to security requirement.
75    ///
76    /// Accepts name for the security requirement which must match to the name of available [`SecurityScheme`].
77    /// Second parameter is [`IntoIterator`] of [`Into<String>`] scopes needed by the [`SecurityRequirement`].
78    /// Scopes must match to the ones defined in [`SecurityScheme`].
79    ///
80    /// # Examples
81    ///
82    /// Make both API keys required:
83    /// ```rust
84    /// # use utoipa::openapi::security::{SecurityRequirement, HttpAuthScheme, HttpBuilder, SecurityScheme};
85    /// # use utoipa::{openapi, Modify, OpenApi};
86    /// # use serde::Serialize;
87    /// #[derive(Debug, Serialize)]
88    /// struct Foo;
89    ///
90    /// impl Modify for Foo {
91    ///     fn modify(&self, openapi: &mut openapi::OpenApi) {
92    ///         if let Some(schema) = openapi.components.as_mut() {
93    ///             schema.add_security_scheme(
94    ///                 "api_key1",
95    ///                 SecurityScheme::Http(
96    ///                     HttpBuilder::new()
97    ///                         .scheme(HttpAuthScheme::Bearer)
98    ///                         .bearer_format("JWT")
99    ///                         .build(),
100    ///                 ),
101    ///             );
102    ///             schema.add_security_scheme(
103    ///                 "api_key2",
104    ///                 SecurityScheme::Http(
105    ///                     HttpBuilder::new()
106    ///                         .scheme(HttpAuthScheme::Bearer)
107    ///                         .bearer_format("JWT")
108    ///                         .build(),
109    ///                 ),
110    ///             );
111    ///         }
112    ///     }
113    /// }
114    ///
115    /// #[derive(Default, OpenApi)]
116    /// #[openapi(
117    ///     modifiers(&Foo),
118    ///     security(
119    ///         ("api_key1" = ["edit:items", "read:items"], "api_key2" = ["edit:items", "read:items"]),
120    ///     )
121    /// )]
122    /// struct ApiDoc;
123    /// ```
124    pub fn add<N: Into<String>, S: IntoIterator<Item = I>, I: Into<String>>(
125        mut self,
126        name: N,
127        scopes: S,
128    ) -> Self {
129        self.value.insert(
130            Into::<String>::into(name),
131            scopes.into_iter().map(Into::<String>::into).collect(),
132        );
133
134        self
135    }
136}
137
138/// OpenAPI [security scheme][security] for path operations.
139///
140/// [security]: https://spec.openapis.org/oas/latest.html#security-scheme-object
141///
142/// # Examples
143///
144/// Create implicit oauth2 flow security schema for path operations.
145/// ```rust
146/// # use utoipa::openapi::security::{SecurityScheme, OAuth2, Implicit, Flow, Scopes};
147/// SecurityScheme::OAuth2(
148///     OAuth2::with_description([Flow::Implicit(
149///         Implicit::new(
150///             "https://localhost/auth/dialog",
151///             Scopes::from_iter([
152///                 ("edit:items", "edit my items"),
153///                 ("read:items", "read my items")
154///             ]),
155///         ),
156///     )], "my oauth2 flow")
157/// );
158/// ```
159///
160/// Create JWT header authentication.
161/// ```rust
162/// # use utoipa::openapi::security::{SecurityScheme, HttpAuthScheme, HttpBuilder};
163/// SecurityScheme::Http(
164///     HttpBuilder::new().scheme(HttpAuthScheme::Bearer).bearer_format("JWT").build()
165/// );
166/// ```
167#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
168#[serde(tag = "type", rename_all = "camelCase")]
169#[cfg_attr(feature = "debug", derive(Debug))]
170pub enum SecurityScheme {
171    /// Oauth flow authentication.
172    #[serde(rename = "oauth2")]
173    OAuth2(OAuth2),
174    /// Api key authentication sent in *`header`*, *`cookie`* or *`query`*.
175    ApiKey(ApiKey),
176    /// Http authentication such as *`bearer`* or *`basic`*.
177    Http(Http),
178    /// Open id connect url to discover OAuth2 configuration values.
179    OpenIdConnect(OpenIdConnect),
180    /// Authentication is done via client side certificate.
181    ///
182    /// OpenApi 3.1 type
183    ///
184    /// # Examples
185    ///
186    /// Declare mutual TLS authentication with a description.
187    /// ```rust
188    /// # use utoipa::openapi::security::SecurityScheme;
189    /// SecurityScheme::MutualTls {
190    ///     description: Some("Client certificate required".to_string()),
191    ///     deprecated: None,
192    ///     extensions: None,
193    /// };
194    /// ```
195    #[serde(rename = "mutualTLS")]
196    MutualTls {
197        /// Optional description explaining how the mutual TLS certificate should be obtained
198        /// and used. Description supports markdown syntax.
199        #[serde(skip_serializing_if = "Option::is_none")]
200        description: Option<String>,
201        /// Declares whether the security scheme is deprecated.
202        #[serde(skip_serializing_if = "Option::is_none")]
203        deprecated: Option<Deprecated>,
204        /// Optional extensions "x-something".
205        #[serde(skip_serializing_if = "Option::is_none", flatten)]
206        extensions: Option<Extensions>,
207    },
208}
209
210/// Api key authentication [`SecurityScheme`].
211#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
212#[serde(tag = "in", rename_all = "lowercase")]
213#[cfg_attr(feature = "debug", derive(Debug))]
214pub enum ApiKey {
215    /// Create api key which is placed in HTTP header.
216    Header(ApiKeyValue),
217    /// Create api key which is placed in query parameters.
218    Query(ApiKeyValue),
219    /// Create api key which is placed in cookie value.
220    Cookie(ApiKeyValue),
221}
222
223/// Value object for [`ApiKey`].
224#[non_exhaustive]
225#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
226#[cfg_attr(feature = "debug", derive(Debug))]
227pub struct ApiKeyValue {
228    /// Name of the [`ApiKey`] parameter.
229    pub name: String,
230
231    /// Description of the the [`ApiKey`] [`SecurityScheme`]. Supports markdown syntax.
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub description: Option<String>,
234
235    /// Declares whether the security scheme is deprecated.
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub deprecated: Option<Deprecated>,
238
239    /// Optional extensions "x-something".
240    #[serde(skip_serializing_if = "Option::is_none", flatten)]
241    pub extensions: Option<Extensions>,
242}
243
244impl ApiKeyValue {
245    /// Constructs new api key value.
246    ///
247    /// # Examples
248    ///
249    /// Create new api key security schema with name `api_key`.
250    /// ```rust
251    /// # use utoipa::openapi::security::ApiKeyValue;
252    /// let api_key = ApiKeyValue::new("api_key");
253    /// ```
254    pub fn new<S: Into<String>>(name: S) -> Self {
255        Self {
256            name: name.into(),
257            description: None,
258            deprecated: None,
259            extensions: Default::default(),
260        }
261    }
262
263    /// Construct a new api key with optional description supporting markdown syntax.
264    ///
265    /// # Examples
266    ///
267    /// Create new api key security schema with name `api_key` with description.
268    /// ```rust
269    /// # use utoipa::openapi::security::ApiKeyValue;
270    /// let api_key = ApiKeyValue::with_description("api_key", "my api_key token");
271    /// ```
272    pub fn with_description<S: Into<String>>(name: S, description: S) -> Self {
273        Self {
274            name: name.into(),
275            description: Some(description.into()),
276            deprecated: None,
277            extensions: Default::default(),
278        }
279    }
280
281    /// Add or change deprecated status.
282    ///
283    /// # Examples
284    ///
285    /// Mark an api key security scheme as deprecated.
286    /// ```rust
287    /// # use utoipa::openapi::security::ApiKeyValue;
288    /// # use utoipa::openapi::Deprecated;
289    /// ApiKeyValue::new("api_key").deprecated(Some(Deprecated::True));
290    /// ```
291    pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
292        self.deprecated = deprecated;
293        self
294    }
295}
296
297builder! {
298    HttpBuilder;
299
300    /// Http authentication [`SecurityScheme`] builder.
301    ///
302    /// Methods can be chained to configure _bearer_format_ or to add _description_.
303    #[non_exhaustive]
304    #[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
305    #[serde(rename_all = "camelCase")]
306    #[cfg_attr(feature = "debug", derive(Debug))]
307    pub struct Http {
308        /// Http authorization scheme in HTTP `Authorization` header value.
309        pub scheme: HttpAuthScheme,
310
311        /// Optional hint to client how the bearer token is formatted. Valid only with [`HttpAuthScheme::Bearer`].
312        #[serde(skip_serializing_if = "Option::is_none")]
313        pub bearer_format: Option<String>,
314
315        /// Optional description of [`Http`] [`SecurityScheme`] supporting markdown syntax.
316        #[serde(skip_serializing_if = "Option::is_none")]
317        pub description: Option<String>,
318
319        /// Declares whether the security scheme is deprecated.
320        #[serde(skip_serializing_if = "Option::is_none")]
321        pub deprecated: Option<Deprecated>,
322
323        /// Optional extensions "x-something".
324        #[serde(skip_serializing_if = "Option::is_none", flatten)]
325        pub extensions: Option<Extensions>,
326    }
327}
328
329impl Http {
330    /// Create new http authentication security schema.
331    ///
332    /// Accepts one argument which defines the scheme of the http authentication.
333    ///
334    /// # Examples
335    ///
336    /// Create http security schema with basic authentication.
337    /// ```rust
338    /// # use utoipa::openapi::security::{SecurityScheme, Http, HttpAuthScheme};
339    /// SecurityScheme::Http(Http::new(HttpAuthScheme::Basic));
340    /// ```
341    pub fn new(scheme: HttpAuthScheme) -> Self {
342        Self {
343            scheme,
344            bearer_format: None,
345            description: None,
346            deprecated: None,
347            extensions: Default::default(),
348        }
349    }
350}
351
352impl HttpBuilder {
353    /// Add or change http authentication scheme used.
354    ///
355    /// # Examples
356    ///
357    /// Create new [`Http`] [`SecurityScheme`] via [`HttpBuilder`].
358    /// ```rust
359    /// # use utoipa::openapi::security::{HttpBuilder, HttpAuthScheme};
360    /// let http = HttpBuilder::new().scheme(HttpAuthScheme::Basic).build();
361    /// ```
362    pub fn scheme(mut self, scheme: HttpAuthScheme) -> Self {
363        self.scheme = scheme;
364
365        self
366    }
367    /// Add or change informative bearer format for http security schema.
368    ///
369    /// This is only applicable to [`HttpAuthScheme::Bearer`].
370    ///
371    /// # Examples
372    ///
373    /// Add JTW bearer format for security schema.
374    /// ```rust
375    /// # use utoipa::openapi::security::{HttpBuilder, HttpAuthScheme};
376    /// HttpBuilder::new().scheme(HttpAuthScheme::Bearer).bearer_format("JWT").build();
377    /// ```
378    pub fn bearer_format<S: Into<String>>(mut self, bearer_format: S) -> Self {
379        if self.scheme == HttpAuthScheme::Bearer {
380            self.bearer_format = Some(bearer_format.into());
381        }
382
383        self
384    }
385
386    /// Add or change optional description supporting markdown syntax.
387    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
388        self.description = description.map(|description| description.into());
389
390        self
391    }
392
393    /// Add or change deprecated status.
394    ///
395    /// # Examples
396    ///
397    /// Mark a bearer HTTP authentication scheme as deprecated.
398    /// ```rust
399    /// # use utoipa::openapi::security::{HttpBuilder, HttpAuthScheme};
400    /// # use utoipa::openapi::Deprecated;
401    /// HttpBuilder::new()
402    ///     .scheme(HttpAuthScheme::Bearer)
403    ///     .bearer_format("JWT")
404    ///     .deprecated(Some(Deprecated::True))
405    ///     .build();
406    /// ```
407    pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
408        self.deprecated = deprecated;
409        self
410    }
411}
412
413/// Implements types according [RFC7235](https://datatracker.ietf.org/doc/html/rfc7235#section-5.1).
414///
415/// Types are maintained at <https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml>.
416#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
417#[cfg_attr(feature = "debug", derive(Debug))]
418#[serde(rename_all = "lowercase")]
419#[allow(missing_docs)]
420pub enum HttpAuthScheme {
421    Basic,
422    Bearer,
423    Digest,
424    Hoba,
425    Mutual,
426    Negotiate,
427    OAuth,
428    #[serde(rename = "scram-sha-1")]
429    ScramSha1,
430    #[serde(rename = "scram-sha-256")]
431    ScramSha256,
432    Vapid,
433}
434
435impl Default for HttpAuthScheme {
436    fn default() -> Self {
437        Self::Basic
438    }
439}
440
441/// Open id connect [`SecurityScheme`].
442#[non_exhaustive]
443#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
444#[serde(rename_all = "camelCase")]
445#[cfg_attr(feature = "debug", derive(Debug))]
446pub struct OpenIdConnect {
447    /// Url of the [`OpenIdConnect`] to discover OAuth2 connect values.
448    pub open_id_connect_url: String,
449
450    /// Description of [`OpenIdConnect`] [`SecurityScheme`] supporting markdown syntax.
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub description: Option<String>,
453
454    /// Declares whether the security scheme is deprecated.
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub deprecated: Option<Deprecated>,
457
458    /// Optional extensions "x-something".
459    #[serde(skip_serializing_if = "Option::is_none", flatten)]
460    pub extensions: Option<Extensions>,
461}
462
463impl OpenIdConnect {
464    /// Construct a new open id connect security schema.
465    ///
466    /// # Examples
467    ///
468    /// ```rust
469    /// # use utoipa::openapi::security::OpenIdConnect;
470    /// OpenIdConnect::new("https://localhost/openid");
471    /// ```
472    pub fn new<S: Into<String>>(open_id_connect_url: S) -> Self {
473        Self {
474            open_id_connect_url: open_id_connect_url.into(),
475            description: None,
476            deprecated: None,
477            extensions: Default::default(),
478        }
479    }
480
481    /// Construct a new [`OpenIdConnect`] [`SecurityScheme`] with optional description
482    /// supporting markdown syntax.
483    ///
484    /// # Examples
485    ///
486    /// ```rust
487    /// # use utoipa::openapi::security::OpenIdConnect;
488    /// OpenIdConnect::with_description("https://localhost/openid", "my pet api open id connect");
489    /// ```
490    pub fn with_description<S: Into<String>>(open_id_connect_url: S, description: S) -> Self {
491        Self {
492            open_id_connect_url: open_id_connect_url.into(),
493            description: Some(description.into()),
494            deprecated: None,
495            extensions: Default::default(),
496        }
497    }
498
499    /// Add or change deprecated status.
500    ///
501    /// # Examples
502    ///
503    /// Mark an OpenID Connect security scheme as deprecated.
504    /// ```rust
505    /// # use utoipa::openapi::security::OpenIdConnect;
506    /// # use utoipa::openapi::Deprecated;
507    /// OpenIdConnect::new("https://localhost/openid").deprecated(Some(Deprecated::True));
508    /// ```
509    pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
510        self.deprecated = deprecated;
511        self
512    }
513}
514
515/// OAuth2 [`Flow`] configuration for [`SecurityScheme`].
516#[non_exhaustive]
517#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
518#[serde(rename_all = "camelCase")]
519#[cfg_attr(feature = "debug", derive(Debug))]
520pub struct OAuth2 {
521    /// Map of supported OAuth2 flows.
522    pub flows: BTreeMap<String, Flow>,
523
524    /// URL to OAuth2 authorization server metadata.
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub oauth2_metadata_url: Option<String>,
527
528    /// Optional description for the [`OAuth2`] [`Flow`] [`SecurityScheme`].
529    #[serde(skip_serializing_if = "Option::is_none")]
530    pub description: Option<String>,
531
532    /// Declares whether the security scheme is deprecated.
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub deprecated: Option<Deprecated>,
535
536    /// Optional extensions "x-something".
537    #[serde(skip_serializing_if = "Option::is_none", flatten)]
538    pub extensions: Option<Extensions>,
539}
540
541impl OAuth2 {
542    /// Construct a new OAuth2 security schema configuration object.
543    ///
544    /// Oauth flow accepts slice of [`Flow`] configuration objects and can be optionally provided with description.
545    ///
546    /// # Examples
547    ///
548    /// Create new OAuth2 flow with multiple authentication flows.
549    /// ```rust
550    /// # use utoipa::openapi::security::{OAuth2, Flow, Password, AuthorizationCode, Scopes};
551    /// OAuth2::new([Flow::Password(
552    ///     Password::with_refresh_url(
553    ///         "https://localhost/oauth/token",
554    ///         Scopes::from_iter([
555    ///             ("edit:items", "edit my items"),
556    ///             ("read:items", "read my items")
557    ///         ]),
558    ///         "https://localhost/refresh/token"
559    ///     )),
560    ///     Flow::AuthorizationCode(
561    ///         AuthorizationCode::new(
562    ///         "https://localhost/authorization/token",
563    ///         "https://localhost/token/url",
564    ///         Scopes::from_iter([
565    ///             ("edit:items", "edit my items"),
566    ///             ("read:items", "read my items")
567    ///         ])),
568    ///    ),
569    /// ]);
570    /// ```
571    pub fn new<I: IntoIterator<Item = Flow>>(flows: I) -> Self {
572        Self {
573            flows: BTreeMap::from_iter(
574                flows
575                    .into_iter()
576                    .map(|auth_flow| (String::from(auth_flow.get_type_as_str()), auth_flow)),
577            ),
578            extensions: None,
579            oauth2_metadata_url: None,
580            description: None,
581            deprecated: None,
582        }
583    }
584
585    /// Construct a new OAuth2 flow with optional description supporting markdown syntax.
586    ///
587    /// # Examples
588    ///
589    /// Create new OAuth2 flow with multiple authentication flows with description.
590    /// ```rust
591    /// # use utoipa::openapi::security::{OAuth2, Flow, Password, AuthorizationCode, Scopes};
592    /// OAuth2::with_description([Flow::Password(
593    ///     Password::with_refresh_url(
594    ///         "https://localhost/oauth/token",
595    ///         Scopes::from_iter([
596    ///             ("edit:items", "edit my items"),
597    ///             ("read:items", "read my items")
598    ///         ]),
599    ///         "https://localhost/refresh/token"
600    ///     )),
601    ///     Flow::AuthorizationCode(
602    ///         AuthorizationCode::new(
603    ///         "https://localhost/authorization/token",
604    ///         "https://localhost/token/url",
605    ///         Scopes::from_iter([
606    ///             ("edit:items", "edit my items"),
607    ///             ("read:items", "read my items")
608    ///         ])
609    ///      ),
610    ///    ),
611    /// ], "my oauth2 flow");
612    /// ```
613    pub fn with_description<I: IntoIterator<Item = Flow>, S: Into<String>>(
614        flows: I,
615        description: S,
616    ) -> Self {
617        Self {
618            flows: BTreeMap::from_iter(
619                flows
620                    .into_iter()
621                    .map(|auth_flow| (String::from(auth_flow.get_type_as_str()), auth_flow)),
622            ),
623            extensions: None,
624            oauth2_metadata_url: None,
625            description: Some(description.into()),
626            deprecated: None,
627        }
628    }
629
630    /// Add OAuth2 authorization server metadata URL.
631    ///
632    /// # Examples
633    ///
634    /// Add authorization server metadata URL to an existing [`OAuth2`] security scheme.
635    /// ```rust
636    /// # use utoipa::openapi::security::{OAuth2, Flow, Password, Scopes};
637    /// OAuth2::new([Flow::Password(
638    ///     Password::new(
639    ///         "https://localhost/oauth/token",
640    ///         Scopes::from_iter([("edit:items", "edit my items")]),
641    ///     ),
642    /// )])
643    /// .with_metadata_url("https://localhost/.well-known/oauth-authorization-server");
644    /// ```
645    pub fn with_metadata_url<S: Into<String>>(mut self, oauth2_metadata_url: S) -> Self {
646        self.oauth2_metadata_url = Some(oauth2_metadata_url.into());
647        self
648    }
649
650    /// Add or change deprecated status.
651    ///
652    /// Declaring a security scheme deprecated signals to API consumers that they should migrate
653    /// away from it. This is an OpenAPI 3.2 addition available on all [`SecurityScheme`] variants.
654    ///
655    /// # Examples
656    ///
657    /// Mark an [`OAuth2`] security scheme as deprecated.
658    /// ```rust
659    /// # use utoipa::openapi::security::{OAuth2, Flow, Password, Scopes};
660    /// # use utoipa::openapi::Deprecated;
661    /// OAuth2::new([Flow::Password(
662    ///     Password::new(
663    ///         "https://localhost/oauth/token",
664    ///         Scopes::from_iter([("edit:items", "edit my items")]),
665    ///     ),
666    /// )])
667    /// .deprecated(Some(Deprecated::True));
668    /// ```
669    pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
670        self.deprecated = deprecated;
671        self
672    }
673}
674
675/// [`OAuth2`] flow configuration object.
676///
677/// See more details at <https://spec.openapis.org/oas/latest.html#oauth-flows-object>.
678#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
679#[serde(untagged)]
680#[cfg_attr(feature = "debug", derive(Debug))]
681pub enum Flow {
682    /// Define implicit [`Flow`] type. See [`Implicit::new`] for usage details.
683    ///
684    /// Soon to be deprecated by <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics>.
685    Implicit(Implicit),
686    /// Define password [`Flow`] type. See [`Password::new`] for usage details.
687    Password(Password),
688    /// Define client credentials [`Flow`] type. See [`ClientCredentials::new`] for usage details.
689    ClientCredentials(ClientCredentials),
690    /// Define authorization code [`Flow`] type. See [`AuthorizationCode::new`] for usage details.
691    AuthorizationCode(AuthorizationCode),
692    /// Define device authorization [`Flow`] type. See [`DeviceAuthorization::new`] for usage details.
693    DeviceAuthorization(DeviceAuthorization),
694}
695
696impl Flow {
697    fn get_type_as_str(&self) -> &str {
698        match self {
699            Self::Implicit(_) => "implicit",
700            Self::Password(_) => "password",
701            Self::ClientCredentials(_) => "clientCredentials",
702            Self::AuthorizationCode(_) => "authorizationCode",
703            Self::DeviceAuthorization(_) => "deviceAuthorization",
704        }
705    }
706}
707
708/// Implicit [`Flow`] configuration for [`OAuth2`].
709#[non_exhaustive]
710#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
711#[serde(rename_all = "camelCase")]
712#[cfg_attr(feature = "debug", derive(Debug))]
713pub struct Implicit {
714    /// Authorization token url for the flow.
715    pub authorization_url: String,
716
717    /// Optional refresh token url for the flow.
718    #[serde(skip_serializing_if = "Option::is_none")]
719    pub refresh_url: Option<String>,
720
721    /// Scopes required by the flow.
722    #[serde(flatten)]
723    pub scopes: Scopes,
724
725    /// Optional extensions "x-something".
726    #[serde(skip_serializing_if = "Option::is_none", flatten)]
727    pub extensions: Option<Extensions>,
728}
729
730impl Implicit {
731    /// Construct a new implicit oauth2 flow.
732    ///
733    /// Accepts two arguments: one which is authorization url and second map of scopes. Scopes can
734    /// also be an empty map.
735    ///
736    /// # Examples
737    ///
738    /// Create new implicit flow with scopes.
739    /// ```rust
740    /// # use utoipa::openapi::security::{Implicit, Scopes};
741    /// Implicit::new(
742    ///     "https://localhost/auth/dialog",
743    ///     Scopes::from_iter([
744    ///         ("edit:items", "edit my items"),
745    ///         ("read:items", "read my items")
746    ///     ]),
747    /// );
748    /// ```
749    ///
750    /// Create new implicit flow without any scopes.
751    /// ```rust
752    /// # use utoipa::openapi::security::{Implicit, Scopes};
753    /// Implicit::new(
754    ///     "https://localhost/auth/dialog",
755    ///     Scopes::new(),
756    /// );
757    /// ```
758    pub fn new<S: Into<String>>(authorization_url: S, scopes: Scopes) -> Self {
759        Self {
760            authorization_url: authorization_url.into(),
761            refresh_url: None,
762            scopes,
763            extensions: Default::default(),
764        }
765    }
766
767    /// Construct a new implicit oauth2 flow with refresh url for getting refresh tokens.
768    ///
769    /// This is essentially same as [`Implicit::new`] but allows defining `refresh_url` for the [`Implicit`]
770    /// oauth2 flow.
771    ///
772    /// # Examples
773    ///
774    /// Create a new implicit oauth2 flow with refresh token.
775    /// ```rust
776    /// # use utoipa::openapi::security::{Implicit, Scopes};
777    /// Implicit::with_refresh_url(
778    ///     "https://localhost/auth/dialog",
779    ///     Scopes::new(),
780    ///     "https://localhost/refresh-token"
781    /// );
782    /// ```
783    pub fn with_refresh_url<S: Into<String>>(
784        authorization_url: S,
785        scopes: Scopes,
786        refresh_url: S,
787    ) -> Self {
788        Self {
789            authorization_url: authorization_url.into(),
790            refresh_url: Some(refresh_url.into()),
791            scopes,
792            extensions: Default::default(),
793        }
794    }
795}
796
797/// Authorization code [`Flow`] configuration for [`OAuth2`].
798#[non_exhaustive]
799#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
800#[serde(rename_all = "camelCase")]
801#[cfg_attr(feature = "debug", derive(Debug))]
802pub struct AuthorizationCode {
803    /// Url for authorization token.
804    pub authorization_url: String,
805    /// Token url for the flow.
806    pub token_url: String,
807
808    /// Optional refresh token url for the flow.
809    #[serde(skip_serializing_if = "Option::is_none")]
810    pub refresh_url: Option<String>,
811
812    /// Scopes required by the flow.
813    #[serde(flatten)]
814    pub scopes: Scopes,
815
816    /// Optional extensions "x-something".
817    #[serde(skip_serializing_if = "Option::is_none", flatten)]
818    pub extensions: Option<Extensions>,
819}
820
821impl AuthorizationCode {
822    /// Construct a new authorization code oauth flow.
823    ///
824    /// Accepts three arguments: one which is authorization url, two a token url and
825    /// three a map of scopes for oauth flow.
826    ///
827    /// # Examples
828    ///
829    /// Create new authorization code flow with scopes.
830    /// ```rust
831    /// # use utoipa::openapi::security::{AuthorizationCode, Scopes};
832    /// AuthorizationCode::new(
833    ///     "https://localhost/auth/dialog",
834    ///     "https://localhost/token",
835    ///     Scopes::from_iter([
836    ///         ("edit:items", "edit my items"),
837    ///         ("read:items", "read my items")
838    ///     ]),
839    /// );
840    /// ```
841    ///
842    /// Create new authorization code flow without any scopes.
843    /// ```rust
844    /// # use utoipa::openapi::security::{AuthorizationCode, Scopes};
845    /// AuthorizationCode::new(
846    ///     "https://localhost/auth/dialog",
847    ///     "https://localhost/token",
848    ///     Scopes::new(),
849    /// );
850    /// ```
851    pub fn new<A: Into<String>, T: Into<String>>(
852        authorization_url: A,
853        token_url: T,
854        scopes: Scopes,
855    ) -> Self {
856        Self {
857            authorization_url: authorization_url.into(),
858            token_url: token_url.into(),
859            refresh_url: None,
860            scopes,
861            extensions: Default::default(),
862        }
863    }
864
865    /// Construct a new  [`AuthorizationCode`] OAuth2 flow with additional refresh token url.
866    ///
867    /// This is essentially same as [`AuthorizationCode::new`] but allows defining extra parameter `refresh_url`
868    /// for fetching refresh token.
869    ///
870    /// # Examples
871    ///
872    /// Create [`AuthorizationCode`] OAuth2 flow with refresh url.
873    /// ```rust
874    /// # use utoipa::openapi::security::{AuthorizationCode, Scopes};
875    /// AuthorizationCode::with_refresh_url(
876    ///     "https://localhost/auth/dialog",
877    ///     "https://localhost/token",
878    ///     Scopes::new(),
879    ///     "https://localhost/refresh-token"
880    /// );
881    /// ```
882    pub fn with_refresh_url<S: Into<String>>(
883        authorization_url: S,
884        token_url: S,
885        scopes: Scopes,
886        refresh_url: S,
887    ) -> Self {
888        Self {
889            authorization_url: authorization_url.into(),
890            token_url: token_url.into(),
891            refresh_url: Some(refresh_url.into()),
892            scopes,
893            extensions: Default::default(),
894        }
895    }
896}
897
898/// Device authorization [`Flow`] configuration for [`OAuth2`].
899#[non_exhaustive]
900#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
901#[serde(rename_all = "camelCase")]
902#[cfg_attr(feature = "debug", derive(Debug))]
903pub struct DeviceAuthorization {
904    /// Device authorization endpoint URL for the flow.
905    pub device_authorization_url: String,
906
907    /// Token URL for the flow.
908    pub token_url: String,
909
910    /// Optional refresh token URL for the flow.
911    #[serde(skip_serializing_if = "Option::is_none")]
912    pub refresh_url: Option<String>,
913
914    /// Scopes required by the flow.
915    #[serde(flatten)]
916    pub scopes: Scopes,
917
918    /// Optional extensions "x-something".
919    #[serde(skip_serializing_if = "Option::is_none", flatten)]
920    pub extensions: Option<Extensions>,
921}
922
923impl DeviceAuthorization {
924    /// Construct a new device authorization OAuth2 flow.
925    ///
926    /// First parameter is the device authorization endpoint URL where the client requests a
927    /// device code, second parameter is the token URL used to poll for the access token, and the
928    /// third parameter defines the scopes available for the flow.
929    ///
930    /// # Examples
931    ///
932    /// Create a device authorization flow and use it in an [`OAuth2`] security scheme.
933    /// ```rust
934    /// # use utoipa::openapi::security::{OAuth2, Flow, DeviceAuthorization, Scopes};
935    /// OAuth2::new([Flow::DeviceAuthorization(
936    ///     DeviceAuthorization::new(
937    ///         "https://localhost/oauth/device/code",
938    ///         "https://localhost/oauth/token",
939    ///         Scopes::from_iter([
940    ///             ("edit:items", "edit my items"),
941    ///             ("read:items", "read my items"),
942    ///         ]),
943    ///     ),
944    /// )]);
945    /// ```
946    pub fn new<D: Into<String>, T: Into<String>>(
947        device_authorization_url: D,
948        token_url: T,
949        scopes: Scopes,
950    ) -> Self {
951        Self {
952            device_authorization_url: device_authorization_url.into(),
953            token_url: token_url.into(),
954            refresh_url: None,
955            scopes,
956            extensions: Default::default(),
957        }
958    }
959
960    /// Construct a new device authorization OAuth2 flow with additional refresh URL.
961    ///
962    /// # Examples
963    ///
964    /// Create a device authorization flow with a refresh token URL.
965    /// ```rust
966    /// # use utoipa::openapi::security::{DeviceAuthorization, Scopes};
967    /// DeviceAuthorization::with_refresh_url(
968    ///     "https://localhost/oauth/device/code",
969    ///     "https://localhost/oauth/token",
970    ///     Scopes::from_iter([("edit:items", "edit my items")]),
971    ///     "https://localhost/oauth/refresh",
972    /// );
973    /// ```
974    pub fn with_refresh_url<S: Into<String>>(
975        device_authorization_url: S,
976        token_url: S,
977        scopes: Scopes,
978        refresh_url: S,
979    ) -> Self {
980        Self {
981            device_authorization_url: device_authorization_url.into(),
982            token_url: token_url.into(),
983            refresh_url: Some(refresh_url.into()),
984            scopes,
985            extensions: Default::default(),
986        }
987    }
988}
989
990/// Password [`Flow`] configuration for [`OAuth2`].
991#[non_exhaustive]
992#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
993#[serde(rename_all = "camelCase")]
994#[cfg_attr(feature = "debug", derive(Debug))]
995pub struct Password {
996    /// Token url for this OAuth2 flow. OAuth2 standard requires TLS.
997    pub token_url: String,
998
999    /// Optional refresh token url.
1000    #[serde(skip_serializing_if = "Option::is_none")]
1001    pub refresh_url: Option<String>,
1002
1003    /// Scopes required by the flow.
1004    #[serde(flatten)]
1005    pub scopes: Scopes,
1006
1007    /// Optional extensions "x-something".
1008    #[serde(skip_serializing_if = "Option::is_none", flatten)]
1009    pub extensions: Option<Extensions>,
1010}
1011
1012impl Password {
1013    /// Construct a new password oauth flow.
1014    ///
1015    /// Accepts two arguments: one which is a token url and
1016    /// two a map of scopes for oauth flow.
1017    ///
1018    /// # Examples
1019    ///
1020    /// Create new password flow with scopes.
1021    /// ```rust
1022    /// # use utoipa::openapi::security::{Password, Scopes};
1023    /// Password::new(
1024    ///     "https://localhost/token",
1025    ///     Scopes::from_iter([
1026    ///         ("edit:items", "edit my items"),
1027    ///         ("read:items", "read my items")
1028    ///     ]),
1029    /// );
1030    /// ```
1031    ///
1032    /// Create new password flow without any scopes.
1033    /// ```rust
1034    /// # use utoipa::openapi::security::{Password, Scopes};
1035    /// Password::new(
1036    ///     "https://localhost/token",
1037    ///     Scopes::new(),
1038    /// );
1039    /// ```
1040    pub fn new<S: Into<String>>(token_url: S, scopes: Scopes) -> Self {
1041        Self {
1042            token_url: token_url.into(),
1043            refresh_url: None,
1044            scopes,
1045            extensions: Default::default(),
1046        }
1047    }
1048
1049    /// Construct a new password oauth flow with additional refresh url.
1050    ///
1051    /// This is essentially same as [`Password::new`] but allows defining third parameter for `refresh_url`
1052    /// for fetching refresh tokens.
1053    ///
1054    /// # Examples
1055    ///
1056    /// Create new password flow with refresh url.
1057    /// ```rust
1058    /// # use utoipa::openapi::security::{Password, Scopes};
1059    /// Password::with_refresh_url(
1060    ///     "https://localhost/token",
1061    ///     Scopes::from_iter([
1062    ///         ("edit:items", "edit my items"),
1063    ///         ("read:items", "read my items")
1064    ///     ]),
1065    ///     "https://localhost/refres-token"
1066    /// );
1067    /// ```
1068    pub fn with_refresh_url<S: Into<String>>(token_url: S, scopes: Scopes, refresh_url: S) -> Self {
1069        Self {
1070            token_url: token_url.into(),
1071            refresh_url: Some(refresh_url.into()),
1072            scopes,
1073            extensions: Default::default(),
1074        }
1075    }
1076}
1077
1078/// Client credentials [`Flow`] configuration for [`OAuth2`].
1079#[non_exhaustive]
1080#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
1081#[serde(rename_all = "camelCase")]
1082#[cfg_attr(feature = "debug", derive(Debug))]
1083pub struct ClientCredentials {
1084    /// Token url used for [`ClientCredentials`] flow. OAuth2 standard requires TLS.
1085    pub token_url: String,
1086
1087    /// Optional refresh token url.
1088    #[serde(skip_serializing_if = "Option::is_none")]
1089    pub refresh_url: Option<String>,
1090
1091    /// Scopes required by the flow.
1092    #[serde(flatten)]
1093    pub scopes: Scopes,
1094
1095    /// Optional extensions "x-something".
1096    #[serde(skip_serializing_if = "Option::is_none", flatten)]
1097    pub extensions: Option<Extensions>,
1098}
1099
1100impl ClientCredentials {
1101    /// Construct a new client credentials oauth flow.
1102    ///
1103    /// Accepts two arguments: one which is a token url and
1104    /// two a map of scopes for oauth flow.
1105    ///
1106    /// # Examples
1107    ///
1108    /// Create new client credentials flow with scopes.
1109    /// ```rust
1110    /// # use utoipa::openapi::security::{ClientCredentials, Scopes};
1111    /// ClientCredentials::new(
1112    ///     "https://localhost/token",
1113    ///     Scopes::from_iter([
1114    ///         ("edit:items", "edit my items"),
1115    ///         ("read:items", "read my items")
1116    ///     ]),
1117    /// );
1118    /// ```
1119    ///
1120    /// Create new client credentials flow without any scopes.
1121    /// ```rust
1122    /// # use utoipa::openapi::security::{ClientCredentials, Scopes};
1123    /// ClientCredentials::new(
1124    ///     "https://localhost/token",
1125    ///     Scopes::new(),
1126    /// );
1127    /// ```
1128    pub fn new<S: Into<String>>(token_url: S, scopes: Scopes) -> Self {
1129        Self {
1130            token_url: token_url.into(),
1131            refresh_url: None,
1132            scopes,
1133            extensions: Default::default(),
1134        }
1135    }
1136
1137    /// Construct a new client credentials oauth flow with additional refresh url.
1138    ///
1139    /// This is essentially same as [`ClientCredentials::new`] but allows defining third parameter for
1140    /// `refresh_url`.
1141    ///
1142    /// # Examples
1143    ///
1144    /// Create new client credentials for with refresh url.
1145    /// ```rust
1146    /// # use utoipa::openapi::security::{ClientCredentials, Scopes};
1147    /// ClientCredentials::with_refresh_url(
1148    ///     "https://localhost/token",
1149    ///     Scopes::from_iter([
1150    ///         ("edit:items", "edit my items"),
1151    ///         ("read:items", "read my items")
1152    ///     ]),
1153    ///     "https://localhost/refresh-url"
1154    /// );
1155    /// ```
1156    pub fn with_refresh_url<S: Into<String>>(token_url: S, scopes: Scopes, refresh_url: S) -> Self {
1157        Self {
1158            token_url: token_url.into(),
1159            refresh_url: Some(refresh_url.into()),
1160            scopes,
1161            extensions: Default::default(),
1162        }
1163    }
1164}
1165
1166/// [`OAuth2`] flow scopes object defines required permissions for oauth flow.
1167///
1168/// Scopes must be given to oauth2 flow but depending on need one of few initialization methods
1169/// could be used.
1170///
1171/// * Create empty map of scopes you can use [`Scopes::new`].
1172/// * Create map with only one scope you can use [`Scopes::one`].
1173/// * Create multiple scopes from iterator with [`Scopes::from_iter`].
1174///
1175/// # Examples
1176///
1177/// Create empty map of scopes.
1178/// ```rust
1179/// # use utoipa::openapi::security::Scopes;
1180/// let scopes = Scopes::new();
1181/// ```
1182///
1183/// Create [`Scopes`] holding one scope.
1184/// ```rust
1185/// # use utoipa::openapi::security::Scopes;
1186/// let scopes = Scopes::one("edit:item", "edit pets");
1187/// ```
1188///
1189/// Create map of scopes from iterator.
1190/// ```rust
1191/// # use utoipa::openapi::security::Scopes;
1192/// let scopes = Scopes::from_iter([
1193///     ("edit:items", "edit my items"),
1194///     ("read:items", "read my items")
1195/// ]);
1196/// ```
1197#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
1198#[cfg_attr(feature = "debug", derive(Debug))]
1199pub struct Scopes {
1200    scopes: BTreeMap<String, String>,
1201}
1202
1203impl Scopes {
1204    /// Construct new [`Scopes`] with empty map of scopes. This is useful if oauth flow does not need
1205    /// any permission scopes.
1206    ///
1207    /// # Examples
1208    ///
1209    /// Create empty map of scopes.
1210    /// ```rust
1211    /// # use utoipa::openapi::security::Scopes;
1212    /// let scopes = Scopes::new();
1213    /// ```
1214    pub fn new() -> Self {
1215        Self {
1216            ..Default::default()
1217        }
1218    }
1219
1220    /// Construct new [`Scopes`] with holding one scope.
1221    ///
1222    /// * `scope` Is be the permission required.
1223    /// * `description` Short description about the permission.
1224    ///
1225    /// # Examples
1226    ///
1227    /// Create map of scopes with one scope item.
1228    /// ```rust
1229    /// # use utoipa::openapi::security::Scopes;
1230    /// let scopes = Scopes::one("edit:item", "edit items");
1231    /// ```
1232    pub fn one<S: Into<String>>(scope: S, description: S) -> Self {
1233        Self {
1234            scopes: BTreeMap::from_iter(iter::once_with(|| (scope.into(), description.into()))),
1235        }
1236    }
1237}
1238
1239impl<I> FromIterator<(I, I)> for Scopes
1240where
1241    I: Into<String>,
1242{
1243    fn from_iter<T: IntoIterator<Item = (I, I)>>(iter: T) -> Self {
1244        Self {
1245            scopes: iter
1246                .into_iter()
1247                .map(|(key, value)| (key.into(), value.into()))
1248                .collect(),
1249        }
1250    }
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255    use super::*;
1256
1257    macro_rules! test_fn {
1258        ($name:ident: $schema:expr; $expected:literal) => {
1259            #[test]
1260            fn $name() {
1261                let value = serde_json::to_value($schema).unwrap();
1262                let expected_value: serde_json::Value = serde_json::from_str($expected).unwrap();
1263
1264                assert_eq!(
1265                    value,
1266                    expected_value,
1267                    "testing serializing \"{}\": \nactual:\n{}\nexpected:\n{}",
1268                    stringify!($name),
1269                    value,
1270                    expected_value
1271                );
1272
1273                println!("{}", &serde_json::to_string_pretty(&$schema).unwrap());
1274            }
1275        };
1276    }
1277
1278    test_fn! {
1279    security_scheme_correct_http_bearer_json:
1280    SecurityScheme::Http(
1281        HttpBuilder::new().scheme(HttpAuthScheme::Bearer).bearer_format("JWT").build()
1282    );
1283    r###"{
1284  "type": "http",
1285  "scheme": "bearer",
1286  "bearerFormat": "JWT"
1287}"###
1288    }
1289
1290    test_fn! {
1291        security_scheme_correct_basic_auth:
1292        SecurityScheme::Http(Http::new(HttpAuthScheme::Basic));
1293        r###"{
1294  "type": "http",
1295  "scheme": "basic"
1296}"###
1297    }
1298
1299    test_fn! {
1300        security_scheme_correct_digest_auth:
1301        SecurityScheme::Http(Http::new(HttpAuthScheme::Digest));
1302        r###"{
1303  "type": "http",
1304  "scheme": "digest"
1305}"###
1306    }
1307
1308    test_fn! {
1309        security_scheme_correct_hoba_auth:
1310        SecurityScheme::Http(Http::new(HttpAuthScheme::Hoba));
1311        r###"{
1312  "type": "http",
1313  "scheme": "hoba"
1314}"###
1315    }
1316
1317    test_fn! {
1318        security_scheme_correct_mutual_auth:
1319        SecurityScheme::Http(Http::new(HttpAuthScheme::Mutual));
1320        r###"{
1321  "type": "http",
1322  "scheme": "mutual"
1323}"###
1324    }
1325
1326    test_fn! {
1327        security_scheme_correct_negotiate_auth:
1328        SecurityScheme::Http(Http::new(HttpAuthScheme::Negotiate));
1329        r###"{
1330  "type": "http",
1331  "scheme": "negotiate"
1332}"###
1333    }
1334
1335    test_fn! {
1336        security_scheme_correct_oauth_auth:
1337        SecurityScheme::Http(Http::new(HttpAuthScheme::OAuth));
1338        r###"{
1339  "type": "http",
1340  "scheme": "oauth"
1341}"###
1342    }
1343
1344    test_fn! {
1345        security_scheme_correct_scram_sha1_auth:
1346        SecurityScheme::Http(Http::new(HttpAuthScheme::ScramSha1));
1347        r###"{
1348  "type": "http",
1349  "scheme": "scram-sha-1"
1350}"###
1351    }
1352
1353    test_fn! {
1354        security_scheme_correct_scram_sha256_auth:
1355        SecurityScheme::Http(Http::new(HttpAuthScheme::ScramSha256));
1356        r###"{
1357  "type": "http",
1358  "scheme": "scram-sha-256"
1359}"###
1360    }
1361
1362    test_fn! {
1363        security_scheme_correct_api_key_cookie_auth:
1364        SecurityScheme::ApiKey(ApiKey::Cookie(ApiKeyValue::new(String::from("api_key"))));
1365        r###"{
1366  "type": "apiKey",
1367  "name": "api_key",
1368  "in": "cookie"
1369}"###
1370    }
1371
1372    test_fn! {
1373        security_scheme_correct_api_key_header_auth:
1374        SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("api_key")));
1375        r###"{
1376  "type": "apiKey",
1377  "name": "api_key",
1378  "in": "header"
1379}"###
1380    }
1381
1382    test_fn! {
1383        security_scheme_correct_api_key_query_auth:
1384        SecurityScheme::ApiKey(ApiKey::Query(ApiKeyValue::new(String::from("api_key"))));
1385        r###"{
1386  "type": "apiKey",
1387  "name": "api_key",
1388  "in": "query"
1389}"###
1390    }
1391
1392    test_fn! {
1393        security_scheme_correct_open_id_connect_auth:
1394        SecurityScheme::OpenIdConnect(OpenIdConnect::new("https://localhost/openid"));
1395        r###"{
1396  "type": "openIdConnect",
1397  "openIdConnectUrl": "https://localhost/openid"
1398}"###
1399    }
1400
1401    test_fn! {
1402        security_scheme_correct_oauth2_implicit:
1403        SecurityScheme::OAuth2(
1404            OAuth2::with_description([Flow::Implicit(
1405                Implicit::new(
1406                    "https://localhost/auth/dialog",
1407                    Scopes::from_iter([
1408                        ("edit:items", "edit my items"),
1409                        ("read:items", "read my items")
1410                    ]),
1411                ),
1412            )], "my oauth2 flow")
1413        );
1414        r###"{
1415  "type": "oauth2",
1416  "flows": {
1417    "implicit": {
1418      "authorizationUrl": "https://localhost/auth/dialog",
1419      "scopes": {
1420        "edit:items": "edit my items",
1421        "read:items": "read my items"
1422      }
1423    }
1424  },
1425  "description": "my oauth2 flow"
1426}"###
1427    }
1428
1429    test_fn! {
1430        security_scheme_correct_oauth2_password:
1431        SecurityScheme::OAuth2(
1432            OAuth2::with_description([Flow::Password(
1433                Password::with_refresh_url(
1434                    "https://localhost/oauth/token",
1435                    Scopes::from_iter([
1436                        ("edit:items", "edit my items"),
1437                        ("read:items", "read my items")
1438                    ]),
1439                    "https://localhost/refresh/token"
1440                ),
1441            )], "my oauth2 flow")
1442        );
1443        r###"{
1444  "type": "oauth2",
1445  "flows": {
1446    "password": {
1447      "tokenUrl": "https://localhost/oauth/token",
1448      "refreshUrl": "https://localhost/refresh/token",
1449      "scopes": {
1450        "edit:items": "edit my items",
1451        "read:items": "read my items"
1452      }
1453    }
1454  },
1455  "description": "my oauth2 flow"
1456}"###
1457    }
1458
1459    test_fn! {
1460        security_scheme_correct_oauth2_client_credentials:
1461        SecurityScheme::OAuth2(
1462            OAuth2::new([Flow::ClientCredentials(
1463                ClientCredentials::with_refresh_url(
1464                    "https://localhost/oauth/token",
1465                    Scopes::from_iter([
1466                        ("edit:items", "edit my items"),
1467                        ("read:items", "read my items")
1468                    ]),
1469                    "https://localhost/refresh/token"
1470                ),
1471            )])
1472        );
1473        r###"{
1474  "type": "oauth2",
1475  "flows": {
1476    "clientCredentials": {
1477      "tokenUrl": "https://localhost/oauth/token",
1478      "refreshUrl": "https://localhost/refresh/token",
1479      "scopes": {
1480        "edit:items": "edit my items",
1481        "read:items": "read my items"
1482      }
1483    }
1484  }
1485}"###
1486    }
1487
1488    test_fn! {
1489        security_scheme_correct_oauth2_authorization_code:
1490        SecurityScheme::OAuth2(
1491            OAuth2::new([Flow::AuthorizationCode(
1492                AuthorizationCode::with_refresh_url(
1493                    "https://localhost/authorization/token",
1494                    "https://localhost/token/url",
1495                    Scopes::from_iter([
1496                        ("edit:items", "edit my items"),
1497                        ("read:items", "read my items")
1498                    ]),
1499                    "https://localhost/refresh/token"
1500                ),
1501            )])
1502        );
1503        r###"{
1504  "type": "oauth2",
1505  "flows": {
1506    "authorizationCode": {
1507      "authorizationUrl": "https://localhost/authorization/token",
1508      "tokenUrl": "https://localhost/token/url",
1509      "refreshUrl": "https://localhost/refresh/token",
1510      "scopes": {
1511        "edit:items": "edit my items",
1512        "read:items": "read my items"
1513      }
1514    }
1515  }
1516}"###
1517    }
1518
1519    test_fn! {
1520        security_scheme_correct_oauth2_authorization_code_no_scopes:
1521        SecurityScheme::OAuth2(
1522            OAuth2::new([Flow::AuthorizationCode(
1523                AuthorizationCode::with_refresh_url(
1524                    "https://localhost/authorization/token",
1525                    "https://localhost/token/url",
1526                    Scopes::new(),
1527                    "https://localhost/refresh/token"
1528                ),
1529            )])
1530        );
1531        r###"{
1532  "type": "oauth2",
1533  "flows": {
1534    "authorizationCode": {
1535      "authorizationUrl": "https://localhost/authorization/token",
1536      "tokenUrl": "https://localhost/token/url",
1537      "refreshUrl": "https://localhost/refresh/token",
1538      "scopes": {}
1539    }
1540  }
1541}"###
1542    }
1543
1544    test_fn! {
1545        security_scheme_correct_mutual_tls:
1546        SecurityScheme::MutualTls {
1547            description: Some(String::from("authorization is performed with client side certificate")),
1548            deprecated: None,
1549            extensions: None,
1550        };
1551        r###"{
1552  "type": "mutualTLS",
1553  "description": "authorization is performed with client side certificate"
1554}"###
1555    }
1556}