silent_openapi/
ui_html.rs1use crate::{Result, SwaggerUiOptions};
8use silent::{Response, StatusCode};
9
10const SWAGGER_UI_VERSION: &str = "5.17.14";
12
13pub fn generate_index_html(
18 ui_path: &str,
19 api_doc_path: &str,
20 options: &SwaggerUiOptions,
21) -> String {
22 let ui_base = ui_path.trim_end_matches('/');
23
24 let (css_href, favicon_href, bundle_src, preset_src) = if cfg!(feature = "swagger-ui-embedded")
25 {
26 (
27 format!("{ui_base}/swagger-ui.css"),
28 format!("{ui_base}/favicon-32x32.png"),
29 format!("{ui_base}/swagger-ui-bundle.js"),
30 format!("{ui_base}/swagger-ui-standalone-preset.js"),
31 )
32 } else {
33 let cdn = format!("https://unpkg.com/swagger-ui-dist@{SWAGGER_UI_VERSION}");
34 (
35 format!("{cdn}/swagger-ui.css"),
36 format!("{cdn}/favicon-32x32.png"),
37 format!("{cdn}/swagger-ui-bundle.js"),
38 format!("{cdn}/swagger-ui-standalone-preset.js"),
39 )
40 };
41
42 let try_it_out = if options.try_it_out_enabled {
43 "true"
44 } else {
45 "false"
46 };
47
48 format!(
49 r#"<!DOCTYPE html>
50<html lang="zh-CN">
51<head>
52 <meta charset="UTF-8">
53 <meta name="viewport" content="width=device-width, initial-scale=1.0">
54 <title>API Documentation - Swagger UI</title>
55 <link rel="stylesheet" type="text/css" href="{css_href}" />
56 <link rel="icon" type="image/png" href="{favicon_href}" sizes="32x32" />
57 <style>
58 html {{
59 box-sizing: border-box;
60 overflow: -moz-scrollbars-vertical;
61 overflow-y: scroll;
62 }}
63 *, *:before, *:after {{
64 box-sizing: inherit;
65 }}
66 body {{
67 margin: 0;
68 background: #fafafa;
69 }}
70 .swagger-ui .topbar {{
71 display: none;
72 }}
73 .swagger-ui .info {{
74 margin: 50px 0;
75 }}
76 .custom-header {{
77 background: #89CFF0;
78 padding: 20px;
79 text-align: center;
80 color: #1976d2;
81 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
82 }}
83 .custom-header h1 {{
84 margin: 0;
85 font-size: 24px;
86 font-weight: 600;
87 }}
88 .custom-header p {{
89 margin: 8px 0 0 0;
90 opacity: 0.8;
91 }}
92 </style>
93</head>
94<body>
95 <div class="custom-header">
96 <h1>Silent Framework API Documentation</h1>
97 <p>OpenAPI 3.0</p>
98 </div>
99 <div id="swagger-ui"></div>
100
101 <script src="{bundle_src}"></script>
102 <script src="{preset_src}"></script>
103 <script>
104 window.onload = function() {{
105 const ui = SwaggerUIBundle({{
106 url: '{api_doc_path}',
107 dom_id: '#swagger-ui',
108 deepLinking: true,
109 presets: [
110 SwaggerUIBundle.presets.apis,
111 SwaggerUIStandalonePreset
112 ],
113 plugins: [
114 SwaggerUIBundle.plugins.DownloadUrl
115 ],
116 layout: "StandaloneLayout",
117 validatorUrl: null,
118 docExpansion: "list",
119 defaultModelsExpandDepth: 1,
120 defaultModelExpandDepth: 1,
121 displayRequestDuration: true,
122 filter: true,
123 showExtensions: true,
124 showCommonExtensions: true,
125 tryItOutEnabled: {try_it_out}
126 }});
127 window.ui = ui;
128 }}
129 </script>
130</body>
131</html>"#
132 )
133}
134
135pub fn serve_asset(#[allow(unused_variables)] asset_path: &str) -> Result<Response> {
140 #[cfg(feature = "swagger-ui-embedded")]
141 {
142 if let Some((content_type, data)) = crate::embedded::get_embedded_asset(asset_path) {
143 let mut response = Response::empty();
144 response.set_status(StatusCode::OK);
145 response.set_header(
146 http::header::CONTENT_TYPE,
147 http::HeaderValue::from_static(content_type),
148 );
149 response.set_header(
150 http::header::CACHE_CONTROL,
151 http::HeaderValue::from_static("public, max-age=86400"),
152 );
153 response.set_body(data.to_vec().into());
154 return Ok(response);
155 }
156 }
157
158 let mut response = Response::empty();
159 response.set_status(StatusCode::NOT_FOUND);
160 response.set_body("Asset not found".into());
161 Ok(response)
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_generate_index_html_contains_api_doc_path() {
170 let html = generate_index_html("/docs", "/docs/openapi.json", &SwaggerUiOptions::default());
171 assert!(html.contains("/docs/openapi.json"));
172 assert!(html.contains("swagger-ui"));
173 }
174
175 #[test]
176 fn test_generate_index_html_try_it_out_disabled() {
177 let options = SwaggerUiOptions {
178 try_it_out_enabled: false,
179 };
180 let html = generate_index_html("/docs", "/docs/openapi.json", &options);
181 assert!(html.contains("tryItOutEnabled: false"));
182 }
183
184 #[test]
185 fn test_generate_index_html_resource_source() {
186 let html = generate_index_html("/docs", "/docs/openapi.json", &SwaggerUiOptions::default());
187
188 if cfg!(feature = "swagger-ui-embedded") {
189 assert!(html.contains("/docs/swagger-ui-bundle.js"));
191 assert!(html.contains("/docs/swagger-ui.css"));
192 } else {
193 assert!(html.contains("unpkg.com/swagger-ui-dist@"));
195 }
196 }
197
198 #[test]
199 fn test_serve_asset_not_found() {
200 let resp = serve_asset("nonexistent.file");
202 assert!(resp.is_ok());
203 }
204}