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