Skip to main content

openapi_ui/
ui.rs

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        // Test light mode
173        let html = generate_docs(json, ThemeMode::Light, None, None).unwrap();
174        assert!(html.contains("Theme Mode API"));
175        assert!(html.contains(":root"));
176
177        // Test dark mode
178        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}