1use crate::error::{Result, UIError};
4use crate::openapi::OpenAPISpec;
5use crate::template::{base_template, template, template_with_custom_theme};
6
7pub use crate::theme::ThemeMode;
8
9#[derive(Debug, Clone)]
11pub struct UIConfig {
12 pub spec: OpenAPISpec,
13 pub theme: String,
14 pub base_url: Option<String>,
15 pub favicon: String,
16}
17
18impl Default for UIConfig {
19 fn default() -> Self {
20 Self {
21 spec: OpenAPISpec {
22 openapi: "3.0.0".to_string(),
23 info: crate::openapi::Info {
24 title: "API Documentation".to_string(),
25 version: "1.0.0".to_string(),
26 description: None,
27 terms_of_service: None,
28 contact: None,
29 license: None,
30 x_logo: None,
31 },
32 servers: vec![],
33 paths: std::collections::HashMap::new(),
34 components: None,
35 security: None,
36 tags: None,
37 external_docs: None,
38 },
39 theme: "system".to_string(),
40 base_url: None,
41 favicon:
42 "https://www.openapis.org/wp-content/uploads/sites/31/2019/06/favicon-140x140.png"
43 .to_string(),
44 }
45 }
46}
47
48pub fn generate_ui_with_config(config: UIConfig) -> String {
50 template(&config.spec, &config.theme, &config.favicon)
51}
52
53pub fn generate_ui(spec: &OpenAPISpec) -> String {
55 let config = UIConfig {
56 spec: spec.clone(),
57 ..Default::default()
58 };
59 template(spec, &config.theme, &config.favicon)
60}
61
62pub fn generate_base_ui() -> String {
64 base_template()
65}
66
67pub struct UIBuilder {
69 config: UIConfig,
70}
71
72impl UIBuilder {
73 pub fn new(spec: OpenAPISpec) -> Self {
75 Self {
76 config: UIConfig {
77 spec,
78 ..Default::default()
79 },
80 }
81 }
82
83 pub fn theme(mut self, theme: &str) -> Self {
85 self.config.theme = theme.to_string();
86 self
87 }
88
89 pub fn base_url(mut self, url: &str) -> Self {
91 self.config.base_url = Some(url.to_string());
92 self
93 }
94
95 pub fn favicon(mut self, url: &str) -> Self {
97 self.config.favicon = url.to_string();
98 self
99 }
100
101 pub fn build(self) -> String {
103 generate_ui_with_config(self.config)
104 }
105}
106
107pub fn generate_docs(
114 json: &str,
115 mode: ThemeMode,
116 custom_css: Option<&str>,
117 favicon: Option<&str>,
118) -> Result<String> {
119 if json.trim().is_empty() {
120 return Ok(generate_base_ui());
121 }
122 let spec: OpenAPISpec = serde_json::from_str(json).map_err(UIError::JsonError)?;
123 let fav = favicon.unwrap_or(
124 "https://www.openapis.org/wp-content/uploads/sites/31/2019/06/favicon-140x140.png",
125 );
126 Ok(template_with_custom_theme(
127 &spec,
128 mode.as_str(),
129 custom_css,
130 fav,
131 ))
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::openapi::Info;
138 use std::collections::HashMap;
139
140 #[test]
141 fn test_generate_ui() {
142 let spec = OpenAPISpec {
143 openapi: "3.0.0".to_string(),
144 info: Info {
145 title: "Test API".to_string(),
146 version: "1.0.0".to_string(),
147 description: Some("A test API".to_string()),
148 terms_of_service: None,
149 contact: None,
150 license: None,
151 x_logo: None,
152 },
153 servers: vec![],
154 paths: HashMap::new(),
155 components: None,
156 security: None,
157 tags: None,
158 external_docs: None,
159 };
160
161 let html = generate_ui(&spec);
162 assert!(html.contains("Test API"));
163 assert!(html.contains("<!doctype html>"));
164 }
165
166 #[test]
167 fn test_generate_docs() {
168 let json = r#"{
169 "openapi": "3.0.0",
170 "info": {
171 "title": "JSON API",
172 "version": "2.0.0"
173 },
174 "paths": {}
175 }"#;
176
177 let result = generate_docs(json, ThemeMode::System, None, None);
178 assert!(result.is_ok());
179 let html = result.unwrap();
180 assert!(html.contains("JSON API"));
181 }
182
183 #[test]
184 fn test_generate_docs_with_theme_mode() {
185 let json = r#"{
186 "openapi": "3.0.0",
187 "info": {
188 "title": "Theme Mode API",
189 "version": "1.0.0"
190 },
191 "paths": {}
192 }"#;
193
194 let html = generate_docs(json, ThemeMode::Light, None, None).unwrap();
196 assert!(html.contains("Theme Mode API"));
197 assert!(html.contains(":root"));
198
199 let html = generate_docs(json, ThemeMode::Dark, None, None).unwrap();
201 assert!(html.contains("Theme Mode API"));
202 assert!(html.contains("[data-theme=\"dark\"]"));
203 }
204
205 #[test]
206 fn test_ui_builder() {
207 let spec = OpenAPISpec {
208 openapi: "3.0.0".to_string(),
209 info: Info {
210 title: "Builder API".to_string(),
211 version: "1.0.0".to_string(),
212 description: None,
213 terms_of_service: None,
214 contact: None,
215 license: None,
216 x_logo: None,
217 },
218 servers: vec![],
219 paths: HashMap::new(),
220 components: None,
221 security: None,
222 tags: None,
223 external_docs: None,
224 };
225
226 let html = UIBuilder::new(spec)
227 .theme("light")
228 .base_url("https://api.example.com")
229 .build();
230
231 assert!(html.contains("Builder API"));
232 }
233
234 #[test]
235 fn test_generate_base_ui() {
236 let html = generate_base_ui();
237 assert!(html.contains("<!doctype html>"));
238 assert!(html.contains("INJECTED_SPEC"));
239 }
240}