utoipa_scalar/
lib.rs

1#![warn(missing_docs)]
2#![warn(rustdoc::broken_intra_doc_links)]
3#![cfg_attr(doc_cfg, feature(doc_cfg))]
4//! This crate works as a bridge between [utoipa](https://docs.rs/utoipa/latest/utoipa/) and [Scalar](https://scalar.com/) OpenAPI visualizer.
5//!
6//! Utoipa-scalar provides simple mechanism to transform OpenAPI spec resource to a servable HTML
7//! file which can be served via [predefined framework integration][Self#examples] or used
8//! [standalone][Self#using-standalone] and served manually.
9//!
10//! You may find fullsize examples from utoipa's Github [repository][examples].
11//!
12//! # Crate Features
13//!
14//! * **actix-web** Allows serving [`Scalar`] via _**`actix-web`**_.
15//! * **rocket** Allows serving [`Scalar`] via _**`rocket`**_.
16//! * **axum** Allows serving [`Scalar`] via _**`axum`**_.
17//!
18//! # Install
19//!
20//! Use Scalar only without any boiler plate implementation.
21//! ```toml
22//! [dependencies]
23//! utoipa-scalar = "0.3"
24//! ```
25//!
26//! Enable actix-web integration with Scalar.
27//! ```toml
28//! [dependencies]
29//! utoipa-scalar = { version = "0.3", features = ["actix-web"] }
30//! ```
31//!
32//! # Using standalone
33//!
34//! Utoipa-scalar can be used standalone as simply as creating a new [`Scalar`] instance and then
35//! serving it by what ever means available as `text/html` from http handler in your favourite web
36//! framework.
37//!
38//! [`Scalar::to_html`] method can be used to convert the [`Scalar`] instance to a servable html
39//! file.
40//! ```
41//! # use utoipa_scalar::Scalar;
42//! # use utoipa::OpenApi;
43//! # use serde_json::json;
44//! # #[derive(OpenApi)]
45//! # #[openapi()]
46//! # struct ApiDoc;
47//! #
48//! let scalar = Scalar::new(ApiDoc::openapi());
49//!
50//! // Then somewhere in your application that handles http operation.
51//! // Make sure you return correct content type `text/html`.
52//! let scalar_handler = move || {
53//!     scalar.to_html()
54//! };
55//! ```
56//!
57//! # Customization
58//!
59//! Scalar supports customization via [`Scalar::custom_html`] method which allows overriding the
60//! default HTML template with customized one.
61//!
62//! **See more about configuration options.**
63//!
64//! * [Quick HTML configuration instructions][html]
65//! * [Configuration options][configuration]
66//! * [Themes][themes]
67//!
68//! The HTML template must contain **`$spec`** variable which will be overridden during
69//! [`Scalar::to_html`] execution.
70//!
71//! * **`$spec`** Will be the [`Spec`] that will be rendered via [Scalar][scalar].
72//!
73//! _**Overriding the HTML template with a custom one.**_
74//! ```rust
75//! # use utoipa_scalar::Scalar;
76//! # use utoipa::OpenApi;
77//! # use serde_json::json;
78//! # #[derive(OpenApi)]
79//! # #[openapi()]
80//! # struct ApiDoc;
81//! #
82//! let html = "...";
83//! Scalar::new(ApiDoc::openapi()).custom_html(html);
84//! ```
85//! # Examples
86//!
87//! _**Serve [`Scalar`] via `actix-web` framework.**_
88//! ```no_run
89//! use actix_web::App;
90//! use utoipa_scalar::{Scalar, Servable};
91//!
92//! # use utoipa::OpenApi;
93//! # use std::net::Ipv4Addr;
94//! # #[derive(OpenApi)]
95//! # #[openapi()]
96//! # struct ApiDoc;
97//! App::new().service(Scalar::with_url("/scalar", ApiDoc::openapi()));
98//! ```
99//!
100//! _**Serve [`Scalar`] via `rocket` framework.**_
101//! ```no_run
102//! # use rocket;
103//! use utoipa_scalar::{Scalar, Servable};
104//!
105//! # use utoipa::OpenApi;
106//! # #[derive(OpenApi)]
107//! # #[openapi()]
108//! # struct ApiDoc;
109//! rocket::build()
110//!     .mount(
111//!         "/",
112//!         Scalar::with_url("/scalar", ApiDoc::openapi()),
113//!     );
114//! ```
115//!
116//! _**Serve [`Scalar`] via `axum` framework.**_
117//!  ```no_run
118//!  use axum::Router;
119//!  use utoipa_scalar::{Scalar, Servable};
120//!  # use utoipa::OpenApi;
121//! # #[derive(OpenApi)]
122//! # #[openapi()]
123//! # struct ApiDoc;
124//! #
125//! # fn inner<S>()
126//! # where
127//! #     S: Clone + Send + Sync + 'static,
128//! # {
129//!
130//!  let app = Router::<S>::new()
131//!      .merge(Scalar::with_url("/scalar", ApiDoc::openapi()));
132//! # }
133//! ```
134//!
135//! _**Use [`Scalar`] to serve custom OpenAPI spec using serde's `json!()` macro.**_
136//! ```rust
137//! # use utoipa_scalar::Scalar;
138//! # use serde_json::json;
139//! Scalar::new(json!({"openapi": "3.1.0"}));
140//! ```
141//!
142//! [examples]: <https://github.com/juhaku/utoipa/tree/master/examples>
143//! [scalar]: <https://scalar.com/>
144//! [configuration]: <https://github.com/scalar/scalar/blob/main/documentation/configuration.md>
145//! [themes]: <https://github.com/scalar/scalar/blob/main/documentation/themes.md>
146//! [html]: <https://github.com/scalar/scalar/blob/main/documentation/integrations/html.md>
147
148use std::borrow::Cow;
149
150use serde::Serialize;
151use serde_json::Value;
152use utoipa::openapi::OpenApi;
153
154mod actix;
155mod axum;
156mod rocket;
157
158const DEFAULT_HTML: &str = include_str!("../res/scalar.html");
159
160/// Trait makes [`Scalar`] to accept an _`URL`_ the [Scalar][scalar] will be served via predefined
161/// web server.
162///
163/// This is used **only** with **`actix-web`**, **`rocket`** or **`axum`** since they have implicit
164/// implementation for serving the [`Scalar`] via the _`URL`_.
165///
166/// [scalar]: <https://scalar.com/>
167#[cfg(any(feature = "actix-web", feature = "rocket", feature = "axum"))]
168#[cfg_attr(
169    doc_cfg,
170    doc(cfg(any(feature = "actix-web", feature = "rocket", feature = "axum")))
171)]
172pub trait Servable<S>
173where
174    S: Spec,
175{
176    /// Construct a new [`Servable`] instance of _`openapi`_ with given _`url`_.
177    ///
178    /// * **url** Must point to location where the [`Servable`] is served.
179    /// * **openapi** Is [`Spec`] that is served via this [`Servable`] from the _**url**_.
180    fn with_url<U: Into<Cow<'static, str>>>(url: U, openapi: S) -> Self;
181}
182
183#[cfg(any(feature = "actix-web", feature = "rocket", feature = "axum"))]
184impl<S: Spec> Servable<S> for Scalar<S> {
185    fn with_url<U: Into<Cow<'static, str>>>(url: U, openapi: S) -> Self {
186        Self {
187            html: Cow::Borrowed(DEFAULT_HTML),
188            url: url.into(),
189            openapi,
190        }
191    }
192}
193
194/// Is standalone instance of [Scalar][scalar].
195///
196/// This can be used together with predefined web framework integration or standalone with
197/// framework of your choice. [`Scalar::to_html`] method will convert this [`Scalar`] instance to
198/// servable HTML file.
199///
200/// [scalar]: <https://scalar.com/>
201#[non_exhaustive]
202#[derive(Clone)]
203pub struct Scalar<S: Spec> {
204    #[allow(unused)]
205    url: Cow<'static, str>,
206    html: Cow<'static, str>,
207    openapi: S,
208}
209
210impl<S: Spec> Scalar<S> {
211    /// Constructs a new [`Scalar`] instance for given _`openapi`_ [`Spec`].
212    ///
213    /// # Examples
214    ///
215    /// _**Create new [`Scalar`] instance.**_
216    /// ```
217    /// # use utoipa_scalar::Scalar;
218    /// # use serde_json::json;
219    /// Scalar::new(json!({"openapi": "3.1.0"}));
220    /// ```
221    pub fn new(openapi: S) -> Self {
222        Self {
223            html: Cow::Borrowed(DEFAULT_HTML),
224            url: Cow::Borrowed("/"),
225            openapi,
226        }
227    }
228
229    /// Converts this [`Scalar`] instance to servable HTML file.
230    ///
231    /// This will replace _**`$spec`**_ variable placeholder with [`Spec`] of this instance
232    /// provided to this instance serializing it to JSON from the HTML template used with the
233    /// [`Scalar`].
234    ///
235    /// At this point in time, it is not possible to customize the HTML template used by the
236    /// [`Scalar`] instance.
237    pub fn to_html(&self) -> String {
238        self.html.replace(
239            "$spec",
240            &serde_json::to_string(&self.openapi).expect(
241                "Invalid OpenAPI spec, expected OpenApi, String, &str or serde_json::Value",
242            ),
243        )
244    }
245
246    /// Override the [default HTML template][scalar_html_quickstart] with new one. Refer to
247    /// [customization] for more comprehensive guide for customization options.
248    ///
249    /// [customization]: <index.html#customization>
250    /// [scalar_html_quickstart]: <https://github.com/scalar/scalar?tab=readme-ov-file#quickstart>
251    pub fn custom_html<H: Into<Cow<'static, str>>>(mut self, html: H) -> Self {
252        self.html = html.into();
253
254        self
255    }
256}
257
258/// Trait defines OpenAPI spec resource types supported by [`Scalar`].
259///
260/// By default this trait is implemented for [`utoipa::openapi::OpenApi`] and [`serde_json::Value`].
261///
262/// * **OpenApi** implementation allows using utoipa's OpenApi struct as a OpenAPI spec resource
263///   for the [`Scalar`].
264/// * **Value** implementation enables the use of arbitrary JSON values with serde's `json!()`
265///   macro as a OpenAPI spec for the [`Scalar`].
266///
267/// # Examples
268///
269/// _**Use [`Scalar`] to serve utoipa's OpenApi.**_
270/// ```no_run
271/// # use utoipa_scalar::Scalar;
272/// # use utoipa::openapi::OpenApiBuilder;
273/// #
274/// Scalar::new(OpenApiBuilder::new().build());
275/// ```
276///
277/// _**Use [`Scalar`] to serve custom OpenAPI spec using serde's `json!()` macro.**_
278/// ```rust
279/// # use utoipa_scalar::Scalar;
280/// # use serde_json::json;
281/// Scalar::new(json!({"openapi": "3.1.0"}));
282/// ```
283pub trait Spec: Serialize {}
284
285impl Spec for OpenApi {}
286
287impl Spec for Value {}