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