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