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}