swagger_ui/
lib.rs

1mod config;
2
3use bytes::Bytes;
4use std::convert::TryInto;
5use std::error::Error;
6use std::fmt::Debug;
7pub use config::{Config, Url};
8use rust_embed::RustEmbed;
9use http::response::Response;
10
11#[derive(RustEmbed)]
12#[folder = "swagger-ui-dist"]
13struct SwaggerUiDist;
14
15#[derive(Debug, Clone)]
16pub struct SwaggerUi {
17    config: Config,
18    prefix: String,
19}
20
21const HTML_MIME: &str = "text/html; charset=utf-8";
22const JS_MIME: &str = "application/javascript; charset=utf-8";
23const CSS_MIME: &str = "text/css; charset=utf-8";
24const PNG_MIME: &str = "image/png";
25const DEFAULT_MIME: &str = "application/octet-stream";
26
27impl SwaggerUi {
28    pub fn url<U: Into<Url>>(mut self, u: U) -> Self {
29        self.config.url(u);
30        self
31    }
32
33    pub fn prefix(mut self, prefix: &str) -> Self {
34        self.prefix = prefix.to_string();
35        self
36    }
37
38    pub fn handle_url<U>(&self, url: U) -> Option<Response<Bytes>>
39        where
40            U: TryInto<http::Uri> + Debug,
41            <U as TryInto<http::Uri>>::Error: Error
42    {
43        let url = url.try_into().unwrap();
44        let path = url.path().strip_prefix(&self.prefix).unwrap();
45        match path {
46            "" | "/" => {
47                let f = SwaggerUiDist::get("index.html").unwrap();
48                let body = f.data.to_vec();
49                Some(Response::builder()
50                    .status(200)
51                    .header("Content-Type", HTML_MIME)
52                    .header("Content-Length", body.len())
53                    .body(body.into())
54                    .unwrap())
55            }
56            "/swagger-initializer.js" => {
57                let f = SwaggerUiDist::get("swagger-initializer.js").unwrap();
58                let body = String::from_utf8(f.data.to_vec()).unwrap();
59                let config = serde_json::to_string(&self.config).unwrap();
60                let body = body.replace("{config}", &config).into_bytes();
61                Some(Response::builder()
62                    .status(200)
63                    .header("Content-Type", JS_MIME)
64                    .header("Content-Length", body.len())
65                    .body(body.into())
66                    .unwrap())
67            }
68            z => {
69                let f = SwaggerUiDist::get(&z[1..])?;
70                let body = f.data.to_vec();
71                let ext = std::path::Path::new(z).extension().unwrap().to_str().unwrap();
72                let mime = match ext {
73                    "html" => HTML_MIME,
74                    "js" => JS_MIME,
75                    "css" => CSS_MIME,
76                    "png" => PNG_MIME,
77                    _ => DEFAULT_MIME,
78                };
79                Some(Response::builder()
80                    .status(200)
81                    .header("Content-Type", mime)
82                    .header("Content-Length", body.len())
83                    .body(body.into())
84                    .unwrap())
85            }
86        }
87    }
88}
89
90impl Default for SwaggerUi {
91    fn default() -> Self {
92        Self {
93            config: Config::default(),
94            prefix: "".to_string(),
95        }
96    }
97}
98
99#[cfg(test)]
100mod test {
101    use super::*;
102
103    #[test]
104    fn test_swagger() {
105        let ui = SwaggerUi::default();
106        let res = ui.handle_url("/").unwrap();
107        assert_eq!(res.status(), 200);
108        assert_eq!(res.headers().get("Content-Type").unwrap(), HTML_MIME);
109
110        let res = ui.handle_url("/swagger-initializer.js").unwrap();
111        assert_eq!(res.status(), 200);
112        assert_eq!(res.headers().get("Content-Type").unwrap(), JS_MIME);
113        let body = String::from_utf8(res.body().to_vec()).unwrap();
114        assert!(!body.contains("{config}"));
115
116        let res = ui.handle_url("/swagger-ui.css").unwrap();
117        assert_eq!(res.status(), 200);
118        assert_eq!(res.headers().get("Content-Type").unwrap(), CSS_MIME);
119
120        assert!(ui.handle_url("/not-found").is_none());
121    }
122
123    #[test]
124    fn test_prefix_stripping() {
125        let ui = SwaggerUi::default()
126            .prefix("/docs");
127
128        let res = ui.handle_url("/docs").unwrap();
129        assert_eq!(res.status(), 200);
130        assert_eq!(res.headers().get("Content-Type").unwrap(), HTML_MIME);
131
132        let res = ui.handle_url("/docs/").unwrap();
133        assert_eq!(res.status(), 200);
134        assert_eq!(res.headers().get("Content-Type").unwrap(), HTML_MIME);
135
136        let res = ui.handle_url("/docs/swagger-initializer.js").unwrap();
137        assert_eq!(res.status(), 200);
138        assert_eq!(res.headers().get("Content-Type").unwrap(), JS_MIME);
139
140    }
141}