1use crate::openapi::OpenAPISpec;
4use crate::theme;
5
6pub const TEMPLATE_HTML: &str = include_str!("index.html");
8
9const SAMPLE_DATA: &str = include_str!("sample_data.json");
10
11pub fn template(spec: &OpenAPISpec, theme_name: &str, favicon: &str) -> String {
13 let spec_json = serde_json::to_string(spec).unwrap_or_default();
14 let js_string = serde_json::to_string(&spec_json).unwrap_or_default();
15
16 let mode = theme::ThemeMode::from_str(theme_name);
17
18 TEMPLATE_HTML
19 .replace("{{light}}", &theme::ThemeMode::Light.get_css())
20 .replace("{{dark}}", &theme::ThemeMode::Dark.get_css())
21 .replace("{{theme}}", mode.as_str())
22 .replace("{{favicon}}", favicon)
23 .replace("/* SPEC_JSON_PLACEHOLDER */ null", &js_string)
24}
25
26pub fn template_with_custom_theme(
28 spec: &OpenAPISpec,
29 theme_name: &str,
30 custom_css: Option<&str>,
31 favicon: &str,
32) -> String {
33 let spec_json = serde_json::to_string(spec).unwrap_or_default();
34 let js_string = serde_json::to_string(&spec_json).unwrap_or_default();
35
36 let mode = theme::ThemeMode::from_str(theme_name);
37 let inject_theme_script = mode == theme::ThemeMode::System;
38
39 let theme_script = if inject_theme_script {
41 r#"<script>(function(){var t=localStorage.getItem("apidocs-theme");if(!t||t==="system"){t=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}document.documentElement.setAttribute("data-theme",t)})()</script>"#
42 } else {
43 ""
44 };
45
46 let light_content = theme::ThemeMode::Light.get_css();
47 let dark_content = theme::ThemeMode::Dark.get_css();
48
49 let mut html = TEMPLATE_HTML
50 .replace("{{light}}", &light_content)
51 .replace("{{dark}}", &dark_content)
52 .replace("{{theme}}", mode.as_str())
53 .replace("{{favicon}}", favicon)
54 .replace("/* SPEC_JSON_PLACEHOLDER */ null", &js_string);
55
56 if inject_theme_script {
57 html = html.replace("<head>", &format!("<head>\n {}", theme_script));
58 }
59
60 if let Some(css) = custom_css {
61 html.replace("</head>", &format!("<style>{}</style></head>", css))
62 } else {
63 html
64 }
65}
66
67pub fn base_template() -> String {
69 let sample_data_js = serde_json::to_string(SAMPLE_DATA).unwrap_or_default();
70
71 TEMPLATE_HTML
72 .replace("{{light}}", &theme::ThemeMode::Light.get_css())
73 .replace("{{dark}}", &theme::ThemeMode::Dark.get_css())
74 .replace("{{theme}}", "system")
75 .replace(
76 "{{favicon}}",
77 "https://www.openapis.org/wp-content/uploads/sites/31/2019/06/favicon-140x140.png",
78 )
79 .replace("/* SPEC_JSON_PLACEHOLDER */ null", "null")
80 .replace("/* SAMPLE_DATA_PLACEHOLDER */ null", &sample_data_js)
81}
82
83pub fn template_with_embedded_theme(
85 spec: &OpenAPISpec,
86 theme_name: &str,
87 favicon: &str,
88) -> String {
89 let spec_json = serde_json::to_string(spec).unwrap_or_default();
90 let js_string = serde_json::to_string(&spec_json).unwrap_or_default();
91
92 let mode = theme::ThemeMode::from_str(theme_name);
93
94 TEMPLATE_HTML
95 .replace("{{light}}", &theme::ThemeMode::Light.get_css())
96 .replace("{{dark}}", &theme::ThemeMode::Dark.get_css())
97 .replace("{{theme}}", mode.as_str())
98 .replace("{{favicon}}", favicon)
99 .replace("/* SPEC_JSON_PLACEHOLDER */ null", &js_string)
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::openapi::{Info, OpenAPISpec};
106 use std::collections::HashMap;
107
108 #[test]
109 fn test_template_generation() {
110 let spec = OpenAPISpec {
111 openapi: "3.0.0".to_string(),
112 info: Info {
113 title: "Test API".to_string(),
114 version: "1.0.0".to_string(),
115 description: Some("A test API".to_string()),
116 terms_of_service: None,
117 contact: None,
118 license: None,
119 x_logo: None,
120 },
121 servers: vec![],
122 paths: HashMap::new(),
123 components: None,
124 security: None,
125 tags: None,
126 external_docs: None,
127 };
128
129 let html = template(&spec, "dark", "favicon.ico");
130 assert!(html.contains("Test API"));
131 assert!(html.contains("3.0.0"));
132 assert!(html.contains("<!doctype html>"));
133 }
134
135 #[test]
136 fn test_template_with_custom_theme() {
137 let spec = OpenAPISpec {
138 openapi: "3.0.0".to_string(),
139 info: Info {
140 title: "Custom Theme API".to_string(),
141 version: "1.0.0".to_string(),
142 description: None,
143 terms_of_service: None,
144 contact: None,
145 license: None,
146 x_logo: None,
147 },
148 servers: vec![],
149 paths: HashMap::new(),
150 components: None,
151 security: None,
152 tags: None,
153 external_docs: None,
154 };
155
156 let custom_css = ":root { --accent: #ff0000; }";
157 let html = template_with_custom_theme(&spec, "light", Some(custom_css), "favicon.ico");
158 assert!(html.contains("Custom Theme API"));
159 }
160
161 #[test]
162 fn test_template_with_system_theme_injects_script() {
163 let spec = OpenAPISpec {
164 openapi: "3.0.0".to_string(),
165 info: Info {
166 title: "System Theme API".to_string(),
167 version: "1.0.0".to_string(),
168 description: None,
169 terms_of_service: None,
170 contact: None,
171 license: None,
172 x_logo: None,
173 },
174 servers: vec![],
175 paths: HashMap::new(),
176 components: None,
177 security: None,
178 tags: None,
179 external_docs: None,
180 };
181
182 let html = template_with_custom_theme(&spec, "system", None, "favicon.ico");
183 assert!(html.contains("apidocs-theme"));
184 assert!(html.contains("prefers-color-scheme"));
185 assert!(html.contains("data-theme=\"system\""));
186 }
187
188 #[test]
189 fn test_system_theme_script_content() {
190 let spec = OpenAPISpec {
191 openapi: "3.0.0".to_string(),
192 info: Info {
193 title: "Test".to_string(),
194 version: "1.0.0".to_string(),
195 description: None,
196 terms_of_service: None,
197 contact: None,
198 license: None,
199 x_logo: None,
200 },
201 servers: vec![],
202 paths: HashMap::new(),
203 components: None,
204 security: None,
205 tags: None,
206 external_docs: None,
207 };
208
209 let html = template_with_custom_theme(&spec, "system", None, "favicon.ico");
210
211 assert!(html.contains("localStorage.getItem(\"apidocs-theme\")"));
212 assert!(html.contains("matchMedia(\"(prefers-color-scheme: dark)\")"));
213
214 assert!(html.contains("if(!t||t===\"system\")"));
215
216 assert!(html.contains("setAttribute(\"data-theme\",t)"));
217
218 }
219
220 #[test]
221 fn test_base_template() {
222 let html = base_template();
223 assert!(html.contains("<!doctype html>"));
224 assert!(html.contains("<html"));
225 assert!(html.contains("INJECTED_SPEC"));
226 }
227
228 #[test]
229 fn test_template_with_embedded_theme() {
230 let spec = OpenAPISpec {
231 openapi: "3.0.0".to_string(),
232 info: Info {
233 title: "Embedded Theme API".to_string(),
234 version: "1.0.0".to_string(),
235 description: None,
236 terms_of_service: None,
237 contact: None,
238 license: None,
239 x_logo: None,
240 },
241 servers: vec![],
242 paths: HashMap::new(),
243 components: None,
244 security: None,
245 tags: None,
246 external_docs: None,
247 };
248
249 let html = template_with_embedded_theme(&spec, "dark", "favicon.ico");
250 assert!(html.contains("Embedded Theme API"));
251 assert!(html.contains("<!doctype html>"));
252 }
253}