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}