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