utoipa_swagger_ui/
lib.rs

1#![cfg_attr(doc_cfg, feature(doc_cfg))]
2//! This crate implements necessary boiler plate code to serve Swagger UI via web server. It
3//! works as a bridge for serving the OpenAPI documentation created with [`utoipa`][utoipa] library in the
4//! Swagger UI.
5//!
6//! [utoipa]: <https://docs.rs/utoipa/>
7//!
8//! **Currently implemented boiler plate for:**
9//!
10//! * **actix-web** `version >= 4`
11//! * **rocket** `version >=0.5`
12//! * **axum** `version >=0.7`
13//!
14//! Serving Swagger UI is framework independent thus this crate also supports serving the Swagger UI with
15//! other frameworks as well. With other frameworks there is bit more manual implementation to be done. See
16//! more details at [`serve`] or [`examples`][examples].
17//!
18//! [examples]: <https://github.com/juhaku/utoipa/tree/master/examples>
19//!
20//! # Crate Features
21//!
22//! * **`actix-web`** Enables `actix-web` integration with pre-configured SwaggerUI service factory allowing
23//!   users to use the Swagger UI without a hassle.
24//! * **`rocket`** Enables `rocket` integration with with pre-configured routes for serving the Swagger UI
25//!   and api doc without a hassle.
26//! * **`axum`** Enables `axum` integration with pre-configured Router serving Swagger UI and OpenAPI specs
27//!   hassle free.
28//! * **`debug-embed`** Enables `debug-embed` feature on `rust_embed` crate to allow embedding files in debug
29//!   builds as well.
30//! * **`reqwest`** Use `reqwest` for downloading Swagger UI according to the `SWAGGER_UI_DOWNLOAD_URL` environment
31//!   variable. This is only enabled by default on _Windows_.
32//! * **`url`** Enabled by default for parsing and encoding the download URL.
33//! * **`vendored`** Enables vendored Swagger UI via `utoipa-swagger-ui-vendored` crate.
34//!
35//! # Install
36//!
37//! Use only the raw types without any boiler plate implementation.
38//! ```toml
39//! [dependencies]
40//! utoipa-swagger-ui = "9"
41//! ```
42//!
43//! Enable actix-web framework with Swagger UI you could define the dependency as follows.
44//! ```toml
45//! [dependencies]
46//! utoipa-swagger-ui = { version = "9", features = ["actix-web"] }
47//! ```
48//!
49//! **Note!** Also remember that you already have defined `utoipa` dependency in your `Cargo.toml`
50//!
51//! ## Build Config
52//!
53//! <div class="warning">
54//!
55//! **Note!** _`utoipa-swagger-ui` crate will by default try to use system `curl` package for downloading the Swagger UI. It
56//! can optionally be downloaded with `reqwest` by enabling `reqwest` feature. On Windows the `reqwest` feature
57//! is enabled by default. Reqwest can be useful for platform independent builds however bringing quite a few
58//! unnecessary dependencies just to download a file. If the `SWAGGER_UI_DOWNLOAD_URL` is a file path then no
59//! downloading will happen._
60//!
61//! </div>
62//!
63//! <div class="warning">
64//!
65//! **Tip!** Use **`vendored`** feature flag to use vendored Swagger UI. This is especially useful for no network
66//! environments.
67//!
68//! </div>
69//!
70//! **The following configuration env variables are available at build time:**
71//!
72//! * `SWAGGER_UI_DOWNLOAD_URL`: Defines the url from where to download the swagger-ui zip file.
73//!
74//!   * Current Swagger UI version: <https://github.com/swagger-api/swagger-ui/archive/refs/tags/v5.17.14.zip>
75//!   * [All available Swagger UI versions](https://github.com/swagger-api/swagger-ui/tags)
76//!
77//! * `SWAGGER_UI_OVERWRITE_FOLDER`: Defines an _optional_ absolute path to a directory containing files
78//!    to overwrite the Swagger UI files. Typically you might want to overwrite `index.html`.
79//!
80//! # Examples
81//!
82//! Serve Swagger UI with api doc via **`actix-web`**. See full example from
83//! [examples](https://github.com/juhaku/utoipa/tree/master/examples/todo-actix).
84//! ```no_run
85//! # use actix_web::{App, HttpServer};
86//! # use utoipa_swagger_ui::SwaggerUi;
87//! # use utoipa::OpenApi;
88//! # use std::net::Ipv4Addr;
89//! # #[derive(OpenApi)]
90//! # #[openapi()]
91//! # struct ApiDoc;
92//! HttpServer::new(move || {
93//!         App::new()
94//!             .service(
95//!                 SwaggerUi::new("/swagger-ui/{_:.*}")
96//!                     .url("/api-docs/openapi.json", ApiDoc::openapi()),
97//!             )
98//!     })
99//!     .bind((Ipv4Addr::UNSPECIFIED, 8989)).unwrap()
100//!     .run();
101//! ```
102//!
103//! Serve Swagger UI with api doc via **`rocket`**. See full example from
104//! [examples](https://github.com/juhaku/utoipa/tree/master/examples/rocket-todo).
105//! ```no_run
106//! # use rocket::{Build, Rocket};
107//! # use utoipa_swagger_ui::SwaggerUi;
108//! # use utoipa::OpenApi;
109//! #[rocket::launch]
110//! fn rocket() -> Rocket<Build> {
111//! #
112//! #     #[derive(OpenApi)]
113//! #     #[openapi()]
114//! #     struct ApiDoc;
115//! #
116//!     rocket::build()
117//!         .mount(
118//!             "/",
119//!             SwaggerUi::new("/swagger-ui/<_..>")
120//!                 .url("/api-docs/openapi.json", ApiDoc::openapi()),
121//!         )
122//! }
123//! ```
124//!
125//! Setup Router to serve Swagger UI with **`axum`** framework. See full implementation of how to serve
126//! Swagger UI with axum from [examples](https://github.com/juhaku/utoipa/tree/master/examples/todo-axum).
127//!```no_run
128//! # use axum::{routing, Router};
129//! # use utoipa_swagger_ui::SwaggerUi;
130//! # use utoipa::OpenApi;
131//!# #[derive(OpenApi)]
132//!# #[openapi()]
133//!# struct ApiDoc;
134//!#
135//!# fn inner<S>()
136//!# where
137//!#     S: Clone + Send + Sync + 'static,
138//!# {
139//! let app = Router::<S>::new()
140//!     .merge(SwaggerUi::new("/swagger-ui")
141//!         .url("/api-docs/openapi.json", ApiDoc::openapi()));
142//!# }
143//! ```
144use std::{borrow::Cow, error::Error, mem, sync::Arc};
145
146mod actix;
147mod axum;
148pub mod oauth;
149mod rocket;
150
151use rust_embed::RustEmbed;
152use serde::Serialize;
153#[cfg(any(feature = "actix-web", feature = "rocket", feature = "axum"))]
154use utoipa::openapi::OpenApi;
155
156include!(concat!(env!("OUT_DIR"), "/embed.rs"));
157
158/// Entry point for serving Swagger UI and api docs in application. It provides
159/// builder style chainable configuration methods for configuring api doc urls.
160///
161/// # Examples
162///
163/// Create new [`SwaggerUi`] with defaults.
164/// ```rust
165/// # use utoipa_swagger_ui::SwaggerUi;
166/// # use utoipa::OpenApi;
167/// # #[derive(OpenApi)]
168/// # #[openapi()]
169/// # struct ApiDoc;
170/// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
171///     .url("/api-docs/openapi.json", ApiDoc::openapi());
172/// ```
173///
174/// Create a new [`SwaggerUi`] with custom [`Config`] and [`oauth::Config`].
175/// ```rust
176/// # use utoipa_swagger_ui::{SwaggerUi, Config, oauth};
177/// # use utoipa::OpenApi;
178/// # #[derive(OpenApi)]
179/// # #[openapi()]
180/// # struct ApiDoc;
181/// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
182///     .url("/api-docs/openapi.json", ApiDoc::openapi())
183///     .config(Config::default().try_it_out_enabled(true).filter(true))
184///     .oauth(oauth::Config::new());
185/// ```
186///
187#[non_exhaustive]
188#[derive(Clone)]
189#[cfg_attr(feature = "debug", derive(Debug))]
190#[cfg(any(feature = "actix-web", feature = "rocket", feature = "axum"))]
191#[cfg_attr(
192    doc_cfg,
193    doc(cfg(any(feature = "actix-web", feature = "rocket", feature = "axum")))
194)]
195pub struct SwaggerUi {
196    path: Cow<'static, str>,
197    urls: Vec<(Url<'static>, OpenApi)>,
198    config: Option<Config<'static>>,
199    external_urls: Vec<(Url<'static>, serde_json::Value)>,
200}
201
202#[cfg(any(feature = "actix-web", feature = "rocket", feature = "axum"))]
203#[cfg_attr(
204    doc_cfg,
205    doc(cfg(any(feature = "actix-web", feature = "rocket", feature = "axum")))
206)]
207impl SwaggerUi {
208    /// Create a new [`SwaggerUi`] for given path.
209    ///
210    /// Path argument will expose the Swagger UI to the user and should be something that
211    /// the underlying application framework / library supports.
212    ///
213    /// # Examples
214    ///
215    /// Exposes Swagger UI using path `/swagger-ui` using actix-web supported syntax.
216    ///
217    /// ```rust
218    /// # use utoipa_swagger_ui::SwaggerUi;
219    /// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}");
220    /// ```
221    pub fn new<P: Into<Cow<'static, str>>>(path: P) -> Self {
222        Self {
223            path: path.into(),
224            urls: Vec::new(),
225            config: None,
226            external_urls: Vec::new(),
227        }
228    }
229
230    /// Add api doc [`Url`] into [`SwaggerUi`].
231    ///
232    /// Method takes two arguments where first one is path which exposes the [`OpenApi`] to the user.
233    /// Second argument is the actual Rust implementation of the OpenAPI doc which is being exposed.
234    ///
235    /// Calling this again will add another url to the Swagger UI.
236    ///
237    /// # Examples
238    ///
239    /// Expose manually created OpenAPI doc.
240    /// ```rust
241    /// # use utoipa_swagger_ui::SwaggerUi;
242    /// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
243    ///     .url("/api-docs/openapi.json", utoipa::openapi::OpenApi::new(
244    ///        utoipa::openapi::Info::new("my application", "0.1.0"),
245    ///        utoipa::openapi::Paths::new(),
246    /// ));
247    /// ```
248    ///
249    /// Expose derived OpenAPI doc.
250    /// ```rust
251    /// # use utoipa_swagger_ui::SwaggerUi;
252    /// # use utoipa::OpenApi;
253    /// # #[derive(OpenApi)]
254    /// # #[openapi()]
255    /// # struct ApiDoc;
256    /// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
257    ///     .url("/api-docs/openapi.json", ApiDoc::openapi());
258    /// ```
259    pub fn url<U: Into<Url<'static>>>(mut self, url: U, openapi: OpenApi) -> Self {
260        self.urls.push((url.into(), openapi));
261
262        self
263    }
264
265    /// Add multiple [`Url`]s to Swagger UI.
266    ///
267    /// Takes one [`Vec`] argument containing tuples of [`Url`] and [`OpenApi`].
268    ///
269    /// Situations where this comes handy is when there is a need or wish to separate different parts
270    /// of the api to separate api docs.
271    ///
272    /// # Examples
273    ///
274    /// Expose multiple api docs via Swagger UI.
275    /// ```rust
276    /// # use utoipa_swagger_ui::{SwaggerUi, Url};
277    /// # use utoipa::OpenApi;
278    /// # #[derive(OpenApi)]
279    /// # #[openapi()]
280    /// # struct ApiDoc;
281    /// # #[derive(OpenApi)]
282    /// # #[openapi()]
283    /// # struct ApiDoc2;
284    /// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
285    ///     .urls(
286    ///       vec![
287    ///          (Url::with_primary("api doc 1", "/api-docs/openapi.json", true), ApiDoc::openapi()),
288    ///          (Url::new("api doc 2", "/api-docs/openapi2.json"), ApiDoc2::openapi())
289    ///     ]
290    /// );
291    /// ```
292    pub fn urls(mut self, urls: Vec<(Url<'static>, OpenApi)>) -> Self {
293        self.urls = urls;
294
295        self
296    }
297
298    /// Add external API doc to the [`SwaggerUi`].
299    ///
300    /// This operation is unchecked and so it does not check any validity of provided content.
301    /// Users are required to do their own check if any regarding validity of the external
302    /// OpenAPI document.
303    ///
304    /// Method accepts two arguments, one is [`Url`] the API doc is served at and the second one is
305    /// the [`serde_json::Value`] of the OpenAPI doc to be served.
306    ///
307    /// # Examples
308    ///
309    /// Add external API doc to the [`SwaggerUi`].
310    /// ```rust
311    /// # use utoipa_swagger_ui::{SwaggerUi, Url};
312    /// # use utoipa::OpenApi;
313    /// # use serde_json::json;
314    /// let external_openapi = json!({"openapi": "3.0.0"});
315    ///
316    /// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
317    ///     .external_url_unchecked("/api-docs/openapi.json", external_openapi);
318    /// ```
319    pub fn external_url_unchecked<U: Into<Url<'static>>>(
320        mut self,
321        url: U,
322        openapi: serde_json::Value,
323    ) -> Self {
324        self.external_urls.push((url.into(), openapi));
325
326        self
327    }
328
329    /// Add external API docs to the [`SwaggerUi`] from iterator.
330    ///
331    /// This operation is unchecked and so it does not check any validity of provided content.
332    /// Users are required to do their own check if any regarding validity of the external
333    /// OpenAPI documents.
334    ///
335    /// Method accepts one argument, an `iter` of [`Url`] and [`serde_json::Value`] tuples. The
336    /// [`Url`] will point to location the OpenAPI document is served and the [`serde_json::Value`]
337    /// is the OpenAPI document to be served.
338    ///
339    /// # Examples
340    ///
341    /// Add external API docs to the [`SwaggerUi`].
342    /// ```rust
343    /// # use utoipa_swagger_ui::{SwaggerUi, Url};
344    /// # use utoipa::OpenApi;
345    /// # use serde_json::json;
346    /// let external_openapi = json!({"openapi": "3.0.0"});
347    /// let external_openapi2 = json!({"openapi": "3.0.0"});
348    ///
349    /// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
350    ///     .external_urls_from_iter_unchecked([
351    ///         ("/api-docs/openapi.json", external_openapi),
352    ///         ("/api-docs/openapi2.json", external_openapi2)
353    ///     ]);
354    /// ```
355    pub fn external_urls_from_iter_unchecked<
356        I: IntoIterator<Item = (U, serde_json::Value)>,
357        U: Into<Url<'static>>,
358    >(
359        mut self,
360        external_urls: I,
361    ) -> Self {
362        self.external_urls.extend(
363            external_urls
364                .into_iter()
365                .map(|(url, doc)| (url.into(), doc)),
366        );
367
368        self
369    }
370
371    /// Add oauth [`oauth::Config`] into [`SwaggerUi`].
372    ///
373    /// Method takes one argument which exposes the [`oauth::Config`] to the user.
374    ///
375    /// # Examples
376    ///
377    /// Enable pkce with default client_id.
378    /// ```rust
379    /// # use utoipa_swagger_ui::{SwaggerUi, oauth};
380    /// # use utoipa::OpenApi;
381    /// # #[derive(OpenApi)]
382    /// # #[openapi()]
383    /// # struct ApiDoc;
384    /// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
385    ///     .url("/api-docs/openapi.json", ApiDoc::openapi())
386    ///     .oauth(oauth::Config::new()
387    ///         .client_id("client-id")
388    ///         .scopes(vec![String::from("openid")])
389    ///         .use_pkce_with_authorization_code_grant(true)
390    ///     );
391    /// ```
392    pub fn oauth(mut self, oauth: oauth::Config) -> Self {
393        let config = self.config.get_or_insert(Default::default());
394        config.oauth = Some(oauth);
395
396        self
397    }
398
399    /// Add custom [`Config`] into [`SwaggerUi`] which gives users more granular control over
400    /// Swagger UI options.
401    ///
402    /// Methods takes one [`Config`] argument which exposes Swagger UI's configurable options
403    /// to the users.
404    ///
405    /// # Examples
406    ///
407    /// Create a new [`SwaggerUi`] with custom configuration.
408    /// ```rust
409    /// # use utoipa_swagger_ui::{SwaggerUi, Config};
410    /// # use utoipa::OpenApi;
411    /// # #[derive(OpenApi)]
412    /// # #[openapi()]
413    /// # struct ApiDoc;
414    /// let swagger = SwaggerUi::new("/swagger-ui/{_:.*}")
415    ///     .url("/api-docs/openapi.json", ApiDoc::openapi())
416    ///     .config(Config::default().try_it_out_enabled(true).filter(true));
417    /// ```
418    pub fn config(mut self, config: Config<'static>) -> Self {
419        self.config = Some(config);
420
421        self
422    }
423}
424
425/// Rust type for Swagger UI url configuration object.
426#[non_exhaustive]
427#[cfg_attr(feature = "debug", derive(Debug))]
428#[derive(Default, Serialize, Clone)]
429pub struct Url<'a> {
430    name: Cow<'a, str>,
431    url: Cow<'a, str>,
432    #[serde(skip)]
433    primary: bool,
434}
435
436impl<'a> Url<'a> {
437    /// Create new [`Url`].
438    ///
439    /// Name is shown in the select dropdown when there are multiple docs in Swagger UI.
440    ///
441    /// Url is path which exposes the OpenAPI doc.
442    ///
443    /// # Examples
444    ///
445    /// ```rust
446    /// # use utoipa_swagger_ui::Url;
447    /// let url = Url::new("My Api", "/api-docs/openapi.json");
448    /// ```
449    pub fn new(name: &'a str, url: &'a str) -> Self {
450        Self {
451            name: Cow::Borrowed(name),
452            url: Cow::Borrowed(url),
453            ..Default::default()
454        }
455    }
456
457    /// Create new [`Url`] with primary flag.
458    ///
459    /// Primary flag allows users to override the default behavior of the Swagger UI for selecting the primary
460    /// doc to display. By default when there are multiple docs in Swagger UI the first one in the list
461    /// will be the primary.
462    ///
463    /// Name is shown in the select dropdown when there are multiple docs in Swagger UI.
464    ///
465    /// Url is path which exposes the OpenAPI doc.
466    ///
467    /// # Examples
468    ///
469    /// Set "My Api" as primary.
470    /// ```rust
471    /// # use utoipa_swagger_ui::Url;
472    /// let url = Url::with_primary("My Api", "/api-docs/openapi.json", true);
473    /// ```
474    pub fn with_primary(name: &'a str, url: &'a str, primary: bool) -> Self {
475        Self {
476            name: Cow::Borrowed(name),
477            url: Cow::Borrowed(url),
478            primary,
479        }
480    }
481}
482
483impl<'a> From<&'a str> for Url<'a> {
484    fn from(url: &'a str) -> Self {
485        Self {
486            url: Cow::Borrowed(url),
487            ..Default::default()
488        }
489    }
490}
491
492impl From<String> for Url<'_> {
493    fn from(url: String) -> Self {
494        Self {
495            url: Cow::Owned(url),
496            ..Default::default()
497        }
498    }
499}
500
501impl<'a> From<Cow<'static, str>> for Url<'a> {
502    fn from(url: Cow<'static, str>) -> Self {
503        Self {
504            url,
505            ..Default::default()
506        }
507    }
508}
509
510const SWAGGER_STANDALONE_LAYOUT: &str = "StandaloneLayout";
511const SWAGGER_BASE_LAYOUT: &str = "BaseLayout";
512
513/// Object used to alter Swagger UI settings.
514///
515/// Config struct provides [Swagger UI configuration](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md)
516/// for settings which could be altered with **docker variables**.
517///
518/// # Examples
519///
520/// In simple case, create config directly from url that points to the api doc json.
521/// ```rust
522/// # use utoipa_swagger_ui::Config;
523/// let config = Config::from("/api-doc.json");
524/// ```
525///
526/// If there is multiple api docs to serve config, the [`Config`] can be also be directly created with [`Config::new`]
527/// ```rust
528/// # use utoipa_swagger_ui::Config;
529/// let config = Config::new(["/api-docs/openapi1.json", "/api-docs/openapi2.json"]);
530/// ```
531///
532/// Or same as above but more verbose syntax.
533/// ```rust
534/// # use utoipa_swagger_ui::{Config, Url};
535/// let config = Config::new([
536///     Url::new("api1", "/api-docs/openapi1.json"),
537///     Url::new("api2", "/api-docs/openapi2.json")
538/// ]);
539/// ```
540///
541/// With oauth config.
542/// ```rust
543/// # use utoipa_swagger_ui::{Config, oauth};
544/// let config = Config::with_oauth_config(
545///     ["/api-docs/openapi1.json", "/api-docs/openapi2.json"],
546///     oauth::Config::new(),
547/// );
548/// ```
549#[non_exhaustive]
550#[derive(Serialize, Clone)]
551#[cfg_attr(feature = "debug", derive(Debug))]
552#[serde(rename_all = "camelCase")]
553pub struct Config<'a> {
554    /// Url to fetch external configuration from.
555    #[serde(skip_serializing_if = "Option::is_none")]
556    config_url: Option<String>,
557
558    /// Id of the DOM element where `Swagger UI` will put it's user interface.
559    #[serde(skip_serializing_if = "Option::is_none")]
560    #[serde(rename = "dom_id")]
561    dom_id: Option<String>,
562
563    /// [`Url`] the Swagger UI is serving.
564    #[serde(skip_serializing_if = "Option::is_none")]
565    url: Option<String>,
566
567    /// Name of the primary url if any.
568    #[serde(skip_serializing_if = "Option::is_none", rename = "urls.primaryName")]
569    urls_primary_name: Option<String>,
570
571    /// [`Url`]s the Swagger UI is serving.
572    #[serde(skip_serializing_if = "Vec::is_empty")]
573    urls: Vec<Url<'a>>,
574
575    /// Enables overriding configuration parameters with url query parameters.
576    #[serde(skip_serializing_if = "Option::is_none")]
577    query_config_enabled: Option<bool>,
578
579    /// Controls whether [deep linking](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/deep-linking.md)
580    /// is enabled in OpenAPI spec.
581    ///
582    /// Deep linking automatically scrolls and expands UI to given url fragment.
583    #[serde(skip_serializing_if = "Option::is_none")]
584    deep_linking: Option<bool>,
585
586    /// Controls whether operation id is shown in the operation list.
587    #[serde(skip_serializing_if = "Option::is_none")]
588    display_operation_id: Option<bool>,
589
590    /// Default models expansion depth; -1 will completely hide the models.
591    #[serde(skip_serializing_if = "Option::is_none")]
592    default_models_expand_depth: Option<isize>,
593
594    /// Default model expansion depth from model example section.
595    #[serde(skip_serializing_if = "Option::is_none")]
596    default_model_expand_depth: Option<isize>,
597
598    /// Defines how models is show when API is first rendered.
599    #[serde(skip_serializing_if = "Option::is_none")]
600    default_model_rendering: Option<String>,
601
602    /// Define whether request duration in milliseconds is displayed for "Try it out" requests.
603    #[serde(skip_serializing_if = "Option::is_none")]
604    display_request_duration: Option<bool>,
605
606    /// Controls default expansion for operations and tags.
607    #[serde(skip_serializing_if = "Option::is_none")]
608    doc_expansion: Option<String>,
609
610    /// Defines is filtering of tagged operations allowed with edit box in top bar.
611    #[serde(skip_serializing_if = "Option::is_none")]
612    filter: Option<bool>,
613
614    /// Controls how many tagged operations are shown. By default all operations are shown.
615    #[serde(skip_serializing_if = "Option::is_none")]
616    max_displayed_tags: Option<usize>,
617
618    /// Defines whether extensions are shown.
619    #[serde(skip_serializing_if = "Option::is_none")]
620    show_extensions: Option<bool>,
621
622    /// Defines whether common extensions are shown.
623    #[serde(skip_serializing_if = "Option::is_none")]
624    show_common_extensions: Option<bool>,
625
626    /// Defines whether "Try it out" section should be enabled by default.
627    #[serde(skip_serializing_if = "Option::is_none")]
628    try_it_out_enabled: Option<bool>,
629
630    /// Defines whether request snippets section is enabled. If disabled legacy curl snipped
631    /// will be used.
632    #[serde(skip_serializing_if = "Option::is_none")]
633    request_snippets_enabled: Option<bool>,
634
635    /// Oauth redirect url.
636    #[serde(skip_serializing_if = "Option::is_none")]
637    oauth2_redirect_url: Option<String>,
638
639    /// Defines whether request mutated with `requestInterceptor` will be used to produce curl command
640    /// in the UI.
641    #[serde(skip_serializing_if = "Option::is_none")]
642    show_mutated_request: Option<bool>,
643
644    /// Define supported http request submit methods.
645    #[serde(skip_serializing_if = "Option::is_none")]
646    supported_submit_methods: Option<Vec<String>>,
647
648    /// Define validator url which is used to validate the Swagger spec. By default the validator swagger.io's
649    /// online validator is used. Setting this to none will disable spec validation.
650    #[serde(skip_serializing_if = "Option::is_none")]
651    validator_url: Option<String>,
652
653    /// Enables passing credentials to CORS requests as defined
654    /// [fetch standards](https://fetch.spec.whatwg.org/#credentials).
655    #[serde(skip_serializing_if = "Option::is_none")]
656    with_credentials: Option<bool>,
657
658    /// Defines whether authorizations is persisted throughout browser refresh and close.
659    #[serde(skip_serializing_if = "Option::is_none")]
660    persist_authorization: Option<bool>,
661
662    /// [`oauth::Config`] the Swagger UI is using for auth flow.
663    #[serde(skip)]
664    oauth: Option<oauth::Config>,
665
666    /// Defines syntax highlighting specific options.
667    #[serde(skip_serializing_if = "Option::is_none")]
668    syntax_highlight: Option<SyntaxHighlight>,
669
670    /// The layout of Swagger UI uses, default is `"StandaloneLayout"`.
671    layout: &'a str,
672
673    /// Basic authentication configuration. If configured, the Swagger UI will prompt for basic auth credentials.
674    #[serde(skip_serializing_if = "Option::is_none")]
675    basic_auth: Option<BasicAuth>,
676}
677
678impl<'a> Config<'a> {
679    fn new_<I: IntoIterator<Item = U>, U: Into<Url<'a>>>(
680        urls: I,
681        oauth_config: Option<oauth::Config>,
682    ) -> Self {
683        let urls = urls.into_iter().map(Into::into).collect::<Vec<Url<'a>>>();
684        let urls_len = urls.len();
685
686        Self {
687            oauth: oauth_config,
688            ..if urls_len == 1 {
689                Self::new_config_with_single_url(urls)
690            } else {
691                Self::new_config_with_multiple_urls(urls)
692            }
693        }
694    }
695
696    fn new_config_with_multiple_urls(urls: Vec<Url<'a>>) -> Self {
697        let primary_name = urls
698            .iter()
699            .find(|url| url.primary)
700            .map(|url| url.name.to_string());
701
702        Self {
703            urls_primary_name: primary_name,
704            urls: urls
705                .into_iter()
706                .map(|mut url| {
707                    if url.name == "" {
708                        url.name = Cow::Owned(String::from(&url.url[..]));
709
710                        url
711                    } else {
712                        url
713                    }
714                })
715                .collect(),
716            ..Default::default()
717        }
718    }
719
720    fn new_config_with_single_url(mut urls: Vec<Url<'a>>) -> Self {
721        let url = urls.get_mut(0).map(mem::take).unwrap();
722        let primary_name = if url.primary {
723            Some(url.name.to_string())
724        } else {
725            None
726        };
727
728        Self {
729            urls_primary_name: primary_name,
730            url: if url.name == "" {
731                Some(url.url.to_string())
732            } else {
733                None
734            },
735            urls: if url.name != "" {
736                vec![url]
737            } else {
738                Vec::new()
739            },
740            ..Default::default()
741        }
742    }
743
744    /// Constructs a new [`Config`] from [`Iterator`] of [`Url`]s.
745    ///
746    /// [`Url`]s provided to the [`Config`] will only change the urls Swagger UI is going to use to
747    /// fetch the API document. This does not change the URL that is defined with [`SwaggerUi::url`]
748    /// or [`SwaggerUi::urls`] which defines the URL the API document is exposed from.
749    ///
750    /// # Examples
751    /// Create new config with 2 api doc urls.
752    /// ```rust
753    /// # use utoipa_swagger_ui::Config;
754    /// let config = Config::new(["/api-docs/openapi1.json", "/api-docs/openapi2.json"]);
755    /// ```
756    pub fn new<I: IntoIterator<Item = U>, U: Into<Url<'a>>>(urls: I) -> Self {
757        Self::new_(urls, None)
758    }
759
760    /// Constructs a new [`Config`] from [`Iterator`] of [`Url`]s.
761    ///
762    /// # Examples
763    /// Create new config with oauth config.
764    /// ```rust
765    /// # use utoipa_swagger_ui::{Config, oauth};
766    /// let config = Config::with_oauth_config(
767    ///     ["/api-docs/openapi1.json", "/api-docs/openapi2.json"],
768    ///     oauth::Config::new(),
769    /// );
770    /// ```
771    pub fn with_oauth_config<I: IntoIterator<Item = U>, U: Into<Url<'a>>>(
772        urls: I,
773        oauth_config: oauth::Config,
774    ) -> Self {
775        Self::new_(urls, Some(oauth_config))
776    }
777
778    /// Configure defaults for current [`Config`].
779    ///
780    /// A new [`Config`] will be created with given `urls` and its _**default values**_ and
781    /// _**url, urls and urls_primary_name**_ will be moved to the current [`Config`] the method
782    /// is called on.
783    ///
784    /// Current config will be returned with configured default values.
785    #[cfg(any(feature = "actix-web", feature = "rocket", feature = "axum"))]
786    #[cfg_attr(
787        doc_cfg,
788        doc(cfg(any(feature = "actix-web", feature = "rocket", feature = "axum")))
789    )]
790    fn configure_defaults<I: IntoIterator<Item = U>, U: Into<Url<'a>>>(mut self, urls: I) -> Self {
791        let Config {
792            dom_id,
793            deep_linking,
794            url,
795            urls,
796            urls_primary_name,
797            ..
798        } = Config::new(urls);
799
800        self.dom_id = dom_id;
801        self.deep_linking = deep_linking;
802        self.url = url;
803        self.urls = urls;
804        self.urls_primary_name = urls_primary_name;
805
806        self
807    }
808
809    /// Add url to fetch external configuration from.
810    ///
811    /// # Examples
812    ///
813    /// Set external config url.
814    /// ```rust
815    /// # use utoipa_swagger_ui::Config;
816    /// let config = Config::new(["/api-docs/openapi.json"])
817    ///     .config_url("http://url.to.external.config");
818    /// ```
819    pub fn config_url<S: Into<String>>(mut self, config_url: S) -> Self {
820        self.config_url = Some(config_url.into());
821
822        self
823    }
824
825    /// Add id of the DOM element where `Swagger UI` will put it's user interface.
826    ///
827    /// The default value is `#swagger-ui`.
828    ///
829    /// # Examples
830    ///
831    /// Set custom dom id where the Swagger UI will place it's content.
832    /// ```rust
833    /// # use utoipa_swagger_ui::Config;
834    /// let config = Config::new(["/api-docs/openapi.json"]).dom_id("#my-id");
835    /// ```
836    pub fn dom_id<S: Into<String>>(mut self, dom_id: S) -> Self {
837        self.dom_id = Some(dom_id.into());
838
839        self
840    }
841
842    /// Set `query_config_enabled` to allow overriding configuration parameters via url `query`
843    /// parameters.
844    ///
845    /// Default value is `false`.
846    ///
847    /// # Examples
848    ///
849    /// Enable query config.
850    /// ```rust
851    /// # use utoipa_swagger_ui::Config;
852    /// let config = Config::new(["/api-docs/openapi.json"])
853    ///     .query_config_enabled(true);
854    /// ```
855    pub fn query_config_enabled(mut self, query_config_enabled: bool) -> Self {
856        self.query_config_enabled = Some(query_config_enabled);
857
858        self
859    }
860
861    /// Set `deep_linking` to allow deep linking tags and operations.
862    ///
863    /// Deep linking will automatically scroll to and expand operation when Swagger UI is
864    /// given corresponding url fragment. See more at
865    /// [deep linking docs](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/deep-linking.md).
866    ///
867    /// Deep linking is enabled by default.
868    ///
869    /// # Examples
870    ///
871    /// Disable the deep linking.
872    /// ```rust
873    /// # use utoipa_swagger_ui::Config;
874    /// let config = Config::new(["/api-docs/openapi.json"])
875    ///     .deep_linking(false);
876    /// ```
877    pub fn deep_linking(mut self, deep_linking: bool) -> Self {
878        self.deep_linking = Some(deep_linking);
879
880        self
881    }
882
883    /// Set `display_operation_id` to `true` to show operation id in the operations list.
884    ///
885    /// Default value is `false`.
886    ///
887    /// # Examples
888    ///
889    /// Allow operation id to be shown.
890    /// ```rust
891    /// # use utoipa_swagger_ui::Config;
892    /// let config = Config::new(["/api-docs/openapi.json"])
893    ///     .display_operation_id(true);
894    /// ```
895    pub fn display_operation_id(mut self, display_operation_id: bool) -> Self {
896        self.display_operation_id = Some(display_operation_id);
897
898        self
899    }
900
901    /// Set 'layout' to 'BaseLayout' to only use the base swagger layout without a search header.
902    ///
903    /// Default value is 'StandaloneLayout'.
904    ///
905    /// # Examples
906    ///
907    /// Configure Swagger to use Base Layout instead of Standalone
908    /// ```rust
909    /// # use utoipa_swagger_ui::Config;
910    /// let config = Config::new(["/api-docs/openapi.json"])
911    ///     .use_base_layout();
912    /// ```
913    pub fn use_base_layout(mut self) -> Self {
914        self.layout = SWAGGER_BASE_LAYOUT;
915
916        self
917    }
918
919    /// Add default models expansion depth.
920    ///
921    /// Setting this to `-1` will completely hide the models.
922    ///
923    /// # Examples
924    ///
925    /// Hide all the models.
926    /// ```rust
927    /// # use utoipa_swagger_ui::Config;
928    /// let config = Config::new(["/api-docs/openapi.json"])
929    ///     .default_models_expand_depth(-1);
930    /// ```
931    pub fn default_models_expand_depth(mut self, default_models_expand_depth: isize) -> Self {
932        self.default_models_expand_depth = Some(default_models_expand_depth);
933
934        self
935    }
936
937    /// Add default model expansion depth for model on the example section.
938    ///
939    /// # Examples
940    ///
941    /// ```rust
942    /// # use utoipa_swagger_ui::Config;
943    /// let config = Config::new(["/api-docs/openapi.json"])
944    ///     .default_model_expand_depth(1);
945    /// ```
946    pub fn default_model_expand_depth(mut self, default_model_expand_depth: isize) -> Self {
947        self.default_model_expand_depth = Some(default_model_expand_depth);
948
949        self
950    }
951
952    /// Add `default_model_rendering` to set how models is show when API is first rendered.
953    ///
954    /// The user can always switch the rendering for given model by clicking the `Model` and `Example Value` links.
955    ///
956    /// * `example` Makes example rendered first by default.
957    /// * `model` Makes model rendered first by default.
958    ///
959    /// # Examples
960    ///
961    /// ```rust
962    /// # use utoipa_swagger_ui::Config;
963    /// let config = Config::new(["/api-docs/openapi.json"])
964    ///     .default_model_rendering(r#"["example"*, "model"]"#);
965    /// ```
966    pub fn default_model_rendering<S: Into<String>>(mut self, default_model_rendering: S) -> Self {
967        self.default_model_rendering = Some(default_model_rendering.into());
968
969        self
970    }
971
972    /// Set to `true` to show request duration of _**'Try it out'**_ requests _**(in milliseconds)**_.
973    ///
974    /// Default value is `false`.
975    ///
976    /// # Examples
977    /// Enable request duration of the _**'Try it out'**_ requests.
978    /// ```rust
979    /// # use utoipa_swagger_ui::Config;
980    /// let config = Config::new(["/api-docs/openapi.json"])
981    ///     .display_request_duration(true);
982    /// ```
983    pub fn display_request_duration(mut self, display_request_duration: bool) -> Self {
984        self.display_request_duration = Some(display_request_duration);
985
986        self
987    }
988
989    /// Add `doc_expansion` to control default expansion for operations and tags.
990    ///
991    /// * `list` Will expand only tags.
992    /// * `full` Will expand tags and operations.
993    /// * `none` Will expand nothing.
994    ///
995    /// # Examples
996    ///
997    /// ```rust
998    /// # use utoipa_swagger_ui::Config;
999    /// let config = Config::new(["/api-docs/openapi.json"])
1000    ///     .doc_expansion(r#"["list"*, "full", "none"]"#);
1001    /// ```
1002    pub fn doc_expansion<S: Into<String>>(mut self, doc_expansion: S) -> Self {
1003        self.doc_expansion = Some(doc_expansion.into());
1004
1005        self
1006    }
1007
1008    /// Add `filter` to allow filtering of tagged operations.
1009    ///
1010    /// When enabled top bar will show and edit box that can be used to filter visible tagged operations.
1011    /// Filter behaves case sensitive manner and matches anywhere inside the tag.
1012    ///
1013    /// Default value is `false`.
1014    ///
1015    /// # Examples
1016    ///
1017    /// Enable filtering.
1018    /// ```rust
1019    /// # use utoipa_swagger_ui::Config;
1020    /// let config = Config::new(["/api-docs/openapi.json"])
1021    ///     .filter(true);
1022    /// ```
1023    pub fn filter(mut self, filter: bool) -> Self {
1024        self.filter = Some(filter);
1025
1026        self
1027    }
1028
1029    /// Add `max_displayed_tags` to restrict shown tagged operations.
1030    ///
1031    /// By default all operations are shown.
1032    ///
1033    /// # Examples
1034    ///
1035    /// Display only 4 operations.
1036    /// ```rust
1037    /// # use utoipa_swagger_ui::Config;
1038    /// let config = Config::new(["/api-docs/openapi.json"])
1039    ///     .max_displayed_tags(4);
1040    /// ```
1041    pub fn max_displayed_tags(mut self, max_displayed_tags: usize) -> Self {
1042        self.max_displayed_tags = Some(max_displayed_tags);
1043
1044        self
1045    }
1046
1047    /// Set `show_extensions` to adjust whether vendor extension _**`(x-)`**_ fields and values
1048    /// are shown for operations, parameters, responses and schemas.
1049    ///
1050    /// # Example
1051    ///
1052    /// Show vendor extensions.
1053    /// ```rust
1054    /// # use utoipa_swagger_ui::Config;
1055    /// let config = Config::new(["/api-docs/openapi.json"])
1056    ///     .show_extensions(true);
1057    /// ```
1058    pub fn show_extensions(mut self, show_extensions: bool) -> Self {
1059        self.show_extensions = Some(show_extensions);
1060
1061        self
1062    }
1063
1064    /// Add `show_common_extensions` to define whether common extension
1065    /// _**`(pattern, maxLength, minLength, maximum, minimum)`**_ fields and values are shown
1066    /// for parameters.
1067    ///
1068    /// # Examples
1069    ///
1070    /// Show common extensions.
1071    /// ```rust
1072    /// # use utoipa_swagger_ui::Config;
1073    /// let config = Config::new(["/api-docs/openapi.json"])
1074    ///     .show_common_extensions(true);
1075    /// ```
1076    pub fn show_common_extensions(mut self, show_common_extensions: bool) -> Self {
1077        self.show_common_extensions = Some(show_common_extensions);
1078
1079        self
1080    }
1081
1082    /// Add `try_it_out_enabled` to enable _**'Try it out'**_ section by default.
1083    ///
1084    /// Default value is `false`.
1085    ///
1086    /// # Examples
1087    ///
1088    /// Enable _**'Try it out'**_ section by default.
1089    /// ```rust
1090    /// # use utoipa_swagger_ui::Config;
1091    /// let config = Config::new(["/api-docs/openapi.json"])
1092    ///     .try_it_out_enabled(true);
1093    /// ```
1094    pub fn try_it_out_enabled(mut self, try_it_out_enabled: bool) -> Self {
1095        self.try_it_out_enabled = Some(try_it_out_enabled);
1096
1097        self
1098    }
1099
1100    /// Set `request_snippets_enabled` to enable request snippets section.
1101    ///
1102    /// If disabled legacy curl snipped will be used.
1103    ///
1104    /// Default value is `false`.
1105    ///
1106    /// # Examples
1107    ///
1108    /// Enable request snippets section.
1109    /// ```rust
1110    /// # use utoipa_swagger_ui::Config;
1111    /// let config = Config::new(["/api-docs/openapi.json"])
1112    ///     .request_snippets_enabled(true);
1113    /// ```
1114    pub fn request_snippets_enabled(mut self, request_snippets_enabled: bool) -> Self {
1115        self.request_snippets_enabled = Some(request_snippets_enabled);
1116
1117        self
1118    }
1119
1120    /// Add oauth redirect url.
1121    ///
1122    /// # Examples
1123    ///
1124    /// Add oauth redirect url.
1125    /// ```rust
1126    /// # use utoipa_swagger_ui::Config;
1127    /// let config = Config::new(["/api-docs/openapi.json"])
1128    ///     .oauth2_redirect_url("http://my.oauth2.redirect.url");
1129    /// ```
1130    pub fn oauth2_redirect_url<S: Into<String>>(mut self, oauth2_redirect_url: S) -> Self {
1131        self.oauth2_redirect_url = Some(oauth2_redirect_url.into());
1132
1133        self
1134    }
1135
1136    /// Add `show_mutated_request` to use request returned from `requestInterceptor`
1137    /// to produce curl command in the UI. If set to `false` the request before `requestInterceptor`
1138    /// was applied will be used.
1139    ///
1140    /// # Examples
1141    ///
1142    /// Use request after `requestInterceptor` to produce the curl command.
1143    /// ```rust
1144    /// # use utoipa_swagger_ui::Config;
1145    /// let config = Config::new(["/api-docs/openapi.json"])
1146    ///     .show_mutated_request(true);
1147    /// ```
1148    pub fn show_mutated_request(mut self, show_mutated_request: bool) -> Self {
1149        self.show_mutated_request = Some(show_mutated_request);
1150
1151        self
1152    }
1153
1154    /// Add supported http methods for _**'Try it out'**_ operation.
1155    ///
1156    /// _**'Try it out'**_ will be enabled based on the given list of http methods when
1157    /// the operation's http method is included within the list.
1158    /// By giving an empty list will disable _**'Try it out'**_ from all operations but it will
1159    /// **not** filter operations from the UI.
1160    ///
1161    /// By default all http operations are enabled.
1162    ///
1163    /// # Examples
1164    ///
1165    /// Set allowed http methods explicitly.
1166    /// ```rust
1167    /// # use utoipa_swagger_ui::Config;
1168    /// let config = Config::new(["/api-docs/openapi.json"])
1169    ///     .supported_submit_methods(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
1170    /// ```
1171    ///
1172    /// Allow _**'Try it out'**_ for only GET operations.
1173    /// ```rust
1174    /// # use utoipa_swagger_ui::Config;
1175    /// let config = Config::new(["/api-docs/openapi.json"])
1176    ///     .supported_submit_methods(["get"]);
1177    /// ```
1178    pub fn supported_submit_methods<I: IntoIterator<Item = S>, S: Into<String>>(
1179        mut self,
1180        supported_submit_methods: I,
1181    ) -> Self {
1182        self.supported_submit_methods = Some(
1183            supported_submit_methods
1184                .into_iter()
1185                .map(|method| method.into())
1186                .collect(),
1187        );
1188
1189        self
1190    }
1191
1192    /// Add validator url which is used to validate the Swagger spec.
1193    ///
1194    /// This can also be set to use locally deployed validator for example see
1195    /// [Validator Badge](https://github.com/swagger-api/validator-badge) for more details.
1196    ///
1197    /// By default swagger.io's online validator _**`(https://validator.swagger.io/validator)`**_ will be used.
1198    /// Setting this to `none` will disable the validator.
1199    ///
1200    /// # Examples
1201    ///
1202    /// Disable the validator.
1203    /// ```rust
1204    /// # use utoipa_swagger_ui::Config;
1205    /// let config = Config::new(["/api-docs/openapi.json"])
1206    ///     .validator_url("none");
1207    /// ```
1208    pub fn validator_url<S: Into<String>>(mut self, validator_url: S) -> Self {
1209        self.validator_url = Some(validator_url.into());
1210
1211        self
1212    }
1213
1214    /// Set `with_credentials` to enable passing credentials to CORS requests send by browser as defined
1215    /// [fetch standards](https://fetch.spec.whatwg.org/#credentials).
1216    ///
1217    /// **Note!** that Swagger UI cannot currently set cookies cross-domain
1218    /// (see [swagger-js#1163](https://github.com/swagger-api/swagger-js/issues/1163)) -
1219    /// as a result, you will have to rely on browser-supplied cookies (which this setting enables sending)
1220    /// that Swagger UI cannot control.
1221    ///
1222    /// # Examples
1223    ///
1224    /// Enable passing credentials to CORS requests.
1225    /// ```rust
1226    /// # use utoipa_swagger_ui::Config;
1227    /// let config = Config::new(["/api-docs/openapi.json"])
1228    ///     .with_credentials(true);
1229    /// ```
1230    pub fn with_credentials(mut self, with_credentials: bool) -> Self {
1231        self.with_credentials = Some(with_credentials);
1232
1233        self
1234    }
1235
1236    /// Set to `true` to enable authorizations to be persisted throughout browser refresh and close.
1237    ///
1238    /// Default value is `false`.
1239    ///
1240    ///
1241    /// # Examples
1242    ///
1243    /// Persists authorization throughout browser close and refresh.
1244    /// ```rust
1245    /// # use utoipa_swagger_ui::Config;
1246    /// let config = Config::new(["/api-docs/openapi.json"])
1247    ///     .persist_authorization(true);
1248    /// ```
1249    pub fn persist_authorization(mut self, persist_authorization: bool) -> Self {
1250        self.persist_authorization = Some(persist_authorization);
1251
1252        self
1253    }
1254
1255    /// Set a specific configuration for syntax highlighting responses
1256    /// and curl commands.
1257    ///
1258    /// By default, swagger-ui does syntax highlighting of responses
1259    /// and curl commands.  This may consume considerable resources in
1260    /// the browser when executed on large responses.
1261    ///
1262    /// # Example
1263    ///
1264    /// Disable syntax highlighting.
1265    /// ```rust
1266    /// # use utoipa_swagger_ui::Config;
1267    /// let config = Config::new(["/api-docs/openapi.json"])
1268    ///     .with_syntax_highlight(false);
1269    /// ```
1270    pub fn with_syntax_highlight<H: Into<SyntaxHighlight>>(mut self, syntax_highlight: H) -> Self {
1271        self.syntax_highlight = Some(syntax_highlight.into());
1272
1273        self
1274    }
1275
1276    /// Set basic authentication configuration.
1277    /// If configured, the Swagger UI will prompt for basic auth credentials.
1278    /// username and password are required. "{username}:{password}" will be base64 encoded and added to the "Authorization" header.
1279    /// If not provided or wrong credentials are provided, the user will be prompted again.
1280    /// # Examples
1281    ///
1282    /// Configure basic authentication.
1283    /// ```rust
1284    /// # use utoipa_swagger_ui::Config;
1285    /// # use utoipa_swagger_ui::BasicAuth;
1286    /// let config = Config::new(["/api-docs/openapi.json"])
1287    ///     .basic_auth(BasicAuth { username: "admin".to_string(), password: "password".to_string() });
1288    /// ```
1289    pub fn basic_auth(mut self, basic_auth: BasicAuth) -> Self {
1290        self.basic_auth = Some(basic_auth);
1291
1292        self
1293    }
1294}
1295
1296impl Default for Config<'_> {
1297    fn default() -> Self {
1298        Self {
1299            config_url: Default::default(),
1300            dom_id: Some("#swagger-ui".to_string()),
1301            url: Default::default(),
1302            urls_primary_name: Default::default(),
1303            urls: Default::default(),
1304            query_config_enabled: Default::default(),
1305            deep_linking: Some(true),
1306            display_operation_id: Default::default(),
1307            default_models_expand_depth: Default::default(),
1308            default_model_expand_depth: Default::default(),
1309            default_model_rendering: Default::default(),
1310            display_request_duration: Default::default(),
1311            doc_expansion: Default::default(),
1312            filter: Default::default(),
1313            max_displayed_tags: Default::default(),
1314            show_extensions: Default::default(),
1315            show_common_extensions: Default::default(),
1316            try_it_out_enabled: Default::default(),
1317            request_snippets_enabled: Default::default(),
1318            oauth2_redirect_url: Default::default(),
1319            show_mutated_request: Default::default(),
1320            supported_submit_methods: Default::default(),
1321            validator_url: Default::default(),
1322            with_credentials: Default::default(),
1323            persist_authorization: Default::default(),
1324            oauth: Default::default(),
1325            syntax_highlight: Default::default(),
1326            layout: SWAGGER_STANDALONE_LAYOUT,
1327            basic_auth: Default::default(),
1328        }
1329    }
1330}
1331
1332impl<'a> From<&'a str> for Config<'a> {
1333    fn from(s: &'a str) -> Self {
1334        Self::new([s])
1335    }
1336}
1337
1338impl From<String> for Config<'_> {
1339    fn from(s: String) -> Self {
1340        Self::new([s])
1341    }
1342}
1343
1344/// Basic auth options for Swagger UI. By providing `BasicAuth` to `Config::basic_auth` the access to the
1345/// Swagger UI can be restricted behind given basic authentication.
1346#[derive(Serialize, Clone)]
1347#[cfg_attr(feature = "debug", derive(Debug))]
1348pub struct BasicAuth {
1349    /// Username for the `BasicAuth`
1350    pub username: String,
1351    /// Password of the _`username`_ for the `BasicAuth`
1352    pub password: String,
1353}
1354
1355/// Represents settings related to syntax highlighting of payloads and
1356/// cURL commands.
1357#[derive(Serialize, Clone)]
1358#[cfg_attr(feature = "debug", derive(Debug))]
1359#[non_exhaustive]
1360pub struct SyntaxHighlight {
1361    /// Boolean telling whether syntax highlighting should be
1362    /// activated or not. Defaults to `true`.
1363    pub activated: bool,
1364    /// Highlight.js syntax coloring theme to use.
1365    #[serde(skip_serializing_if = "Option::is_none")]
1366    pub theme: Option<&'static str>,
1367}
1368
1369impl Default for SyntaxHighlight {
1370    fn default() -> Self {
1371        Self {
1372            activated: true,
1373            theme: None,
1374        }
1375    }
1376}
1377
1378impl From<bool> for SyntaxHighlight {
1379    fn from(value: bool) -> Self {
1380        Self {
1381            activated: value,
1382            ..Default::default()
1383        }
1384    }
1385}
1386
1387impl SyntaxHighlight {
1388    /// Explicitly specifies whether syntax highlighting is to be
1389    /// activated or not.  Defaults to true.
1390    pub fn activated(mut self, activated: bool) -> Self {
1391        self.activated = activated;
1392        self
1393    }
1394
1395    /// Explicitly specifies the
1396    /// [Highlight.js](https://highlightjs.org/) coloring theme to
1397    /// utilize for syntax highlighting.
1398    pub fn theme(mut self, theme: &'static str) -> Self {
1399        self.theme = Some(theme);
1400        self
1401    }
1402}
1403
1404/// Represents servable file of Swagger UI. This is used together with [`serve`] function
1405/// to serve Swagger UI files via web server.
1406#[non_exhaustive]
1407pub struct SwaggerFile<'a> {
1408    /// Content of the file as [`Cow`] [`slice`] of bytes.
1409    pub bytes: Cow<'a, [u8]>,
1410    /// Content type of the file e.g `"text/xml"`.
1411    pub content_type: String,
1412}
1413
1414/// User friendly way to serve Swagger UI and its content via web server.
1415///
1416/// * **path** Should be the relative path to Swagger UI resource within the web server.
1417/// * **config** Swagger [`Config`] to use for the Swagger UI.
1418///
1419/// Typically this function is implemented _**within**_ handler what serves the Swagger UI. Handler itself must
1420/// match to user defined path that points to the root of the Swagger UI and match everything relatively
1421/// from the root of the Swagger UI _**(tail path)**_. The relative path from root of the Swagger UI
1422/// is used to serve [`SwaggerFile`]s. If Swagger UI is served from path `/swagger-ui/` then the `tail`
1423/// is everything under the `/swagger-ui/` prefix.
1424///
1425/// _There are also implementations in [examples of utoipa repository][examples]._
1426///
1427/// [examples]: https://github.com/juhaku/utoipa/tree/master/examples
1428///
1429/// # Examples
1430///
1431/// _**Reference implementation with `actix-web`.**_
1432/// ```rust
1433/// # use actix_web::HttpResponse;
1434/// # use std::sync::Arc;
1435/// # use utoipa_swagger_ui::Config;
1436/// // The config should be created in main function or in initialization before
1437/// // creation of the handler which will handle serving the Swagger UI.
1438/// let config = Arc::new(Config::from("/api-doc.json"));
1439///
1440/// // This "/" is for demonstrative purposes only. The actual path should point to
1441/// // file within Swagger UI. In real implementation this is the `tail` path from root of the
1442/// // Swagger UI to the file served.
1443/// let tail_path = "/";
1444///
1445/// fn get_swagger_ui(tail_path: String, config: Arc<Config>) -> HttpResponse {
1446///   match utoipa_swagger_ui::serve(tail_path.as_ref(), config) {
1447///       Ok(swagger_file) => swagger_file
1448///           .map(|file| {
1449///               HttpResponse::Ok()
1450///                   .content_type(file.content_type)
1451///                   .body(file.bytes.to_vec())
1452///           })
1453///           .unwrap_or_else(|| HttpResponse::NotFound().finish()),
1454///       Err(error) => HttpResponse::InternalServerError().body(error.to_string()),
1455///   }
1456/// }
1457/// ```
1458pub fn serve<'a>(
1459    path: &str,
1460    config: Arc<Config<'a>>,
1461) -> Result<Option<SwaggerFile<'a>>, Box<dyn Error>> {
1462    let mut file_path = path;
1463
1464    if file_path.is_empty() || file_path == "/" {
1465        file_path = "index.html";
1466    }
1467
1468    if let Some(file) = SwaggerUiDist::get(file_path) {
1469        let mut bytes = file.data;
1470
1471        if file_path == "swagger-initializer.js" {
1472            let mut file = match String::from_utf8(bytes.to_vec()) {
1473                Ok(file) => file,
1474                Err(error) => return Err(Box::new(error)),
1475            };
1476
1477            file = format_config(config.as_ref(), file)?;
1478
1479            if let Some(oauth) = &config.oauth {
1480                match oauth::format_swagger_config(oauth, file) {
1481                    Ok(oauth_file) => file = oauth_file,
1482                    Err(error) => return Err(Box::new(error)),
1483                }
1484            }
1485
1486            bytes = Cow::Owned(file.as_bytes().to_vec())
1487        };
1488
1489        Ok(Some(SwaggerFile {
1490            bytes,
1491            content_type: mime_guess::from_path(file_path)
1492                .first_or_octet_stream()
1493                .to_string(),
1494        }))
1495    } else {
1496        Ok(None)
1497    }
1498}
1499
1500#[inline]
1501fn format_config(config: &Config, file: String) -> Result<String, Box<dyn Error>> {
1502    let config_json = match serde_json::to_string_pretty(&config) {
1503        Ok(config) => config,
1504        Err(error) => return Err(Box::new(error)),
1505    };
1506
1507    // Replace {{config}} with pretty config json and remove the curly brackets `{ }` from beginning and the end.
1508    Ok(file.replace("{{config}}", &config_json[2..&config_json.len() - 2]))
1509}
1510
1511/// Is used to provide general way to deliver multiple types of OpenAPI docs via `utoipa-swagger-ui`.
1512#[cfg(any(feature = "actix-web", feature = "rocket", feature = "axum"))]
1513#[derive(Clone)]
1514enum ApiDoc {
1515    Utoipa(utoipa::openapi::OpenApi),
1516    Value(serde_json::Value),
1517}
1518
1519// Delegate serde's `Serialize` to the variant itself.
1520#[cfg(any(feature = "actix-web", feature = "rocket", feature = "axum"))]
1521impl Serialize for ApiDoc {
1522    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1523    where
1524        S: serde::Serializer,
1525    {
1526        match self {
1527            Self::Value(value) => value.serialize(serializer),
1528            Self::Utoipa(utoipa) => utoipa.serialize(serializer),
1529        }
1530    }
1531}
1532
1533#[cfg(test)]
1534mod tests {
1535    use similar::TextDiff;
1536
1537    use super::*;
1538
1539    fn assert_diff_equal(expected: &str, new: &str) {
1540        let diff = TextDiff::from_lines(expected, new);
1541
1542        assert_eq!(expected, new, "\nDifference:\n{}", diff.unified_diff());
1543    }
1544
1545    const TEST_INITIAL_CONFIG: &str = r#"
1546window.ui = SwaggerUIBundle({
1547  {{config}},
1548  presets: [
1549    SwaggerUIBundle.presets.apis,
1550    SwaggerUIStandalonePreset
1551  ],
1552  plugins: [
1553    SwaggerUIBundle.plugins.DownloadUrl
1554  ],
1555});"#;
1556
1557    #[test]
1558    fn format_swagger_config_json_single_url() {
1559        let formatted_config = match format_config(
1560            &Config::new(["/api-docs/openapi1.json"]),
1561            String::from(TEST_INITIAL_CONFIG),
1562        ) {
1563            Ok(file) => file,
1564            Err(error) => panic!("{error}"),
1565        };
1566
1567        const EXPECTED: &str = r###"
1568window.ui = SwaggerUIBundle({
1569    "dom_id": "#swagger-ui",
1570  "url": "/api-docs/openapi1.json",
1571  "deepLinking": true,
1572  "layout": "StandaloneLayout",
1573  presets: [
1574    SwaggerUIBundle.presets.apis,
1575    SwaggerUIStandalonePreset
1576  ],
1577  plugins: [
1578    SwaggerUIBundle.plugins.DownloadUrl
1579  ],
1580});"###;
1581
1582        assert_diff_equal(EXPECTED, &formatted_config)
1583    }
1584
1585    #[test]
1586    fn format_swagger_config_json_single_url_with_name() {
1587        let formatted_config = match format_config(
1588            &Config::new([Url::new("api-doc1", "/api-docs/openapi1.json")]),
1589            String::from(TEST_INITIAL_CONFIG),
1590        ) {
1591            Ok(file) => file,
1592            Err(error) => panic!("{error}"),
1593        };
1594
1595        const EXPECTED: &str = r###"
1596window.ui = SwaggerUIBundle({
1597    "dom_id": "#swagger-ui",
1598  "urls": [
1599    {
1600      "name": "api-doc1",
1601      "url": "/api-docs/openapi1.json"
1602    }
1603  ],
1604  "deepLinking": true,
1605  "layout": "StandaloneLayout",
1606  presets: [
1607    SwaggerUIBundle.presets.apis,
1608    SwaggerUIStandalonePreset
1609  ],
1610  plugins: [
1611    SwaggerUIBundle.plugins.DownloadUrl
1612  ],
1613});"###;
1614
1615        assert_diff_equal(EXPECTED, &formatted_config);
1616    }
1617
1618    #[test]
1619    fn format_swagger_config_json_single_url_primary() {
1620        let formatted_config = match format_config(
1621            &Config::new([Url::with_primary(
1622                "api-doc1",
1623                "/api-docs/openapi1.json",
1624                true,
1625            )]),
1626            String::from(TEST_INITIAL_CONFIG),
1627        ) {
1628            Ok(file) => file,
1629            Err(error) => panic!("{error}"),
1630        };
1631
1632        const EXPECTED: &str = r###"
1633window.ui = SwaggerUIBundle({
1634    "dom_id": "#swagger-ui",
1635  "urls.primaryName": "api-doc1",
1636  "urls": [
1637    {
1638      "name": "api-doc1",
1639      "url": "/api-docs/openapi1.json"
1640    }
1641  ],
1642  "deepLinking": true,
1643  "layout": "StandaloneLayout",
1644  presets: [
1645    SwaggerUIBundle.presets.apis,
1646    SwaggerUIStandalonePreset
1647  ],
1648  plugins: [
1649    SwaggerUIBundle.plugins.DownloadUrl
1650  ],
1651});"###;
1652
1653        assert_diff_equal(EXPECTED, &formatted_config);
1654    }
1655
1656    #[test]
1657    fn format_swagger_config_multiple_urls_with_primary() {
1658        let formatted_config = match format_config(
1659            &Config::new([
1660                Url::with_primary("api-doc1", "/api-docs/openapi1.json", true),
1661                Url::new("api-doc2", "/api-docs/openapi2.json"),
1662            ]),
1663            String::from(TEST_INITIAL_CONFIG),
1664        ) {
1665            Ok(file) => file,
1666            Err(error) => panic!("{error}"),
1667        };
1668
1669        const EXPECTED: &str = r###"
1670window.ui = SwaggerUIBundle({
1671    "dom_id": "#swagger-ui",
1672  "urls.primaryName": "api-doc1",
1673  "urls": [
1674    {
1675      "name": "api-doc1",
1676      "url": "/api-docs/openapi1.json"
1677    },
1678    {
1679      "name": "api-doc2",
1680      "url": "/api-docs/openapi2.json"
1681    }
1682  ],
1683  "deepLinking": true,
1684  "layout": "StandaloneLayout",
1685  presets: [
1686    SwaggerUIBundle.presets.apis,
1687    SwaggerUIStandalonePreset
1688  ],
1689  plugins: [
1690    SwaggerUIBundle.plugins.DownloadUrl
1691  ],
1692});"###;
1693
1694        assert_diff_equal(EXPECTED, &formatted_config);
1695    }
1696
1697    #[test]
1698    fn format_swagger_config_multiple_urls() {
1699        let formatted_config = match format_config(
1700            &Config::new(["/api-docs/openapi1.json", "/api-docs/openapi2.json"]),
1701            String::from(TEST_INITIAL_CONFIG),
1702        ) {
1703            Ok(file) => file,
1704            Err(error) => panic!("{error}"),
1705        };
1706
1707        const EXPECTED: &str = r###"
1708window.ui = SwaggerUIBundle({
1709    "dom_id": "#swagger-ui",
1710  "urls": [
1711    {
1712      "name": "/api-docs/openapi1.json",
1713      "url": "/api-docs/openapi1.json"
1714    },
1715    {
1716      "name": "/api-docs/openapi2.json",
1717      "url": "/api-docs/openapi2.json"
1718    }
1719  ],
1720  "deepLinking": true,
1721  "layout": "StandaloneLayout",
1722  presets: [
1723    SwaggerUIBundle.presets.apis,
1724    SwaggerUIStandalonePreset
1725  ],
1726  plugins: [
1727    SwaggerUIBundle.plugins.DownloadUrl
1728  ],
1729});"###;
1730
1731        assert_diff_equal(EXPECTED, &formatted_config);
1732    }
1733
1734    #[test]
1735    fn format_swagger_config_with_multiple_fields() {
1736        let formatted_config = match format_config(
1737            &Config::new(["/api-docs/openapi1.json"])
1738                .deep_linking(false)
1739                .dom_id("#another-el")
1740                .default_model_expand_depth(-1)
1741                .default_model_rendering(r#"["example"*]"#)
1742                .default_models_expand_depth(1)
1743                .display_operation_id(true)
1744                .display_request_duration(true)
1745                .filter(true)
1746                .use_base_layout()
1747                .doc_expansion(r#"["list"*]"#)
1748                .max_displayed_tags(1)
1749                .oauth2_redirect_url("http://auth")
1750                .persist_authorization(true)
1751                .query_config_enabled(true)
1752                .request_snippets_enabled(true)
1753                .show_common_extensions(true)
1754                .show_extensions(true)
1755                .show_mutated_request(true)
1756                .supported_submit_methods(["get"])
1757                .try_it_out_enabled(true)
1758                .validator_url("none")
1759                .with_credentials(true),
1760            String::from(TEST_INITIAL_CONFIG),
1761        ) {
1762            Ok(file) => file,
1763            Err(error) => panic!("{error}"),
1764        };
1765
1766        const EXPECTED: &str = r###"
1767window.ui = SwaggerUIBundle({
1768    "dom_id": "#another-el",
1769  "url": "/api-docs/openapi1.json",
1770  "queryConfigEnabled": true,
1771  "deepLinking": false,
1772  "displayOperationId": true,
1773  "defaultModelsExpandDepth": 1,
1774  "defaultModelExpandDepth": -1,
1775  "defaultModelRendering": "[\"example\"*]",
1776  "displayRequestDuration": true,
1777  "docExpansion": "[\"list\"*]",
1778  "filter": true,
1779  "maxDisplayedTags": 1,
1780  "showExtensions": true,
1781  "showCommonExtensions": true,
1782  "tryItOutEnabled": true,
1783  "requestSnippetsEnabled": true,
1784  "oauth2RedirectUrl": "http://auth",
1785  "showMutatedRequest": true,
1786  "supportedSubmitMethods": [
1787    "get"
1788  ],
1789  "validatorUrl": "none",
1790  "withCredentials": true,
1791  "persistAuthorization": true,
1792  "layout": "BaseLayout",
1793  presets: [
1794    SwaggerUIBundle.presets.apis,
1795    SwaggerUIStandalonePreset
1796  ],
1797  plugins: [
1798    SwaggerUIBundle.plugins.DownloadUrl
1799  ],
1800});"###;
1801
1802        assert_diff_equal(EXPECTED, &formatted_config);
1803    }
1804
1805    #[test]
1806    fn format_swagger_config_with_syntax_highlight_default() {
1807        let formatted_config = match format_config(
1808            &Config::new(["/api-docs/openapi1.json"])
1809                .with_syntax_highlight(SyntaxHighlight::default()),
1810            String::from(TEST_INITIAL_CONFIG),
1811        ) {
1812            Ok(file) => file,
1813            Err(error) => panic!("{error}"),
1814        };
1815
1816        const EXPECTED: &str = r###"
1817window.ui = SwaggerUIBundle({
1818    "dom_id": "#swagger-ui",
1819  "url": "/api-docs/openapi1.json",
1820  "deepLinking": true,
1821  "syntaxHighlight": {
1822    "activated": true
1823  },
1824  "layout": "StandaloneLayout",
1825  presets: [
1826    SwaggerUIBundle.presets.apis,
1827    SwaggerUIStandalonePreset
1828  ],
1829  plugins: [
1830    SwaggerUIBundle.plugins.DownloadUrl
1831  ],
1832});"###;
1833
1834        assert_diff_equal(EXPECTED, &formatted_config);
1835    }
1836
1837    #[test]
1838    fn format_swagger_config_with_syntax_highlight_on() {
1839        let formatted_config = match format_config(
1840            &Config::new(["/api-docs/openapi1.json"]).with_syntax_highlight(true),
1841            String::from(TEST_INITIAL_CONFIG),
1842        ) {
1843            Ok(file) => file,
1844            Err(error) => panic!("{error}"),
1845        };
1846
1847        const EXPECTED: &str = r###"
1848window.ui = SwaggerUIBundle({
1849    "dom_id": "#swagger-ui",
1850  "url": "/api-docs/openapi1.json",
1851  "deepLinking": true,
1852  "syntaxHighlight": {
1853    "activated": true
1854  },
1855  "layout": "StandaloneLayout",
1856  presets: [
1857    SwaggerUIBundle.presets.apis,
1858    SwaggerUIStandalonePreset
1859  ],
1860  plugins: [
1861    SwaggerUIBundle.plugins.DownloadUrl
1862  ],
1863});"###;
1864
1865        assert_diff_equal(EXPECTED, &formatted_config);
1866    }
1867
1868    #[test]
1869    fn format_swagger_config_with_syntax_highlight_off() {
1870        let formatted_config = match format_config(
1871            &Config::new(["/api-docs/openapi1.json"]).with_syntax_highlight(false),
1872            String::from(TEST_INITIAL_CONFIG),
1873        ) {
1874            Ok(file) => file,
1875            Err(error) => panic!("{error}"),
1876        };
1877
1878        const EXPECTED: &str = r###"
1879window.ui = SwaggerUIBundle({
1880    "dom_id": "#swagger-ui",
1881  "url": "/api-docs/openapi1.json",
1882  "deepLinking": true,
1883  "syntaxHighlight": {
1884    "activated": false
1885  },
1886  "layout": "StandaloneLayout",
1887  presets: [
1888    SwaggerUIBundle.presets.apis,
1889    SwaggerUIStandalonePreset
1890  ],
1891  plugins: [
1892    SwaggerUIBundle.plugins.DownloadUrl
1893  ],
1894});"###;
1895
1896        assert_diff_equal(EXPECTED, &formatted_config);
1897    }
1898
1899    #[test]
1900    fn format_swagger_config_with_syntax_highlight_default_with_theme() {
1901        let formatted_config = match format_config(
1902            &Config::new(["/api-docs/openapi1.json"])
1903                .with_syntax_highlight(SyntaxHighlight::default().theme("monokai")),
1904            String::from(TEST_INITIAL_CONFIG),
1905        ) {
1906            Ok(file) => file,
1907            Err(error) => panic!("{error}"),
1908        };
1909
1910        const EXPECTED: &str = r###"
1911window.ui = SwaggerUIBundle({
1912    "dom_id": "#swagger-ui",
1913  "url": "/api-docs/openapi1.json",
1914  "deepLinking": true,
1915  "syntaxHighlight": {
1916    "activated": true,
1917    "theme": "monokai"
1918  },
1919  "layout": "StandaloneLayout",
1920  presets: [
1921    SwaggerUIBundle.presets.apis,
1922    SwaggerUIStandalonePreset
1923  ],
1924  plugins: [
1925    SwaggerUIBundle.plugins.DownloadUrl
1926  ],
1927});"###;
1928
1929        assert_diff_equal(EXPECTED, &formatted_config);
1930    }
1931}