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}