armature_openapi/
swagger.rs1use crate::spec::OpenApiSpec;
4use armature_core::{Error, HttpResponse};
5
6#[derive(Debug, Clone)]
8pub struct SwaggerConfig {
9 pub path: String,
11 pub title: String,
13 pub spec: OpenApiSpec,
15}
16
17impl SwaggerConfig {
18 pub fn new(path: impl Into<String>, spec: OpenApiSpec) -> Self {
20 Self {
21 path: path.into(),
22 title: "API Documentation".to_string(),
23 spec,
24 }
25 }
26
27 pub fn with_title(mut self, title: impl Into<String>) -> Self {
29 self.title = title.into();
30 self
31 }
32
33 pub fn spec_json(&self) -> Result<String, Error> {
35 serde_json::to_string_pretty(&self.spec)
36 .map_err(|e| Error::Internal(format!("Failed to serialize spec: {}", e)))
37 }
38
39 pub fn spec_yaml(&self) -> Result<String, Error> {
41 serde_yaml::to_string(&self.spec)
42 .map_err(|e| Error::Internal(format!("Failed to serialize spec: {}", e)))
43 }
44}
45
46pub fn swagger_ui_response(config: &SwaggerConfig) -> Result<HttpResponse, Error> {
53 let spec_json = config.spec_json()?;
54 let spec_json_safe = spec_json.replace("</", "<\\/");
61
62 let html = format!(
63 r#"<!DOCTYPE html>
64<html lang="en">
65<head>
66 <meta charset="UTF-8">
67 <meta name="viewport" content="width=device-width, initial-scale=1.0">
68 <title>{title}</title>
69 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.10.0/swagger-ui.css">
70 <style>
71 body {{
72 margin: 0;
73 padding: 0;
74 }}
75 </style>
76</head>
77<body>
78 <div id="swagger-ui"></div>
79 <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.10.0/swagger-ui-bundle.js"></script>
80 <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.10.0/swagger-ui-standalone-preset.js"></script>
81 <script>
82 window.onload = function() {{
83 const spec = {spec};
84 SwaggerUIBundle({{
85 spec: spec,
86 dom_id: '#swagger-ui',
87 deepLinking: true,
88 presets: [
89 SwaggerUIBundle.presets.apis,
90 SwaggerUIStandalonePreset
91 ],
92 plugins: [
93 SwaggerUIBundle.plugins.DownloadUrl
94 ],
95 layout: "StandaloneLayout"
96 }});
97 }};
98 </script>
99</body>
100</html>"#,
101 title = config.title,
102 spec = spec_json_safe
103 );
104
105 Ok(HttpResponse::ok()
106 .with_header("content-type".to_string(), "text/html".to_string())
107 .with_body(html.into_bytes()))
108}
109
110pub fn spec_json_response(config: &SwaggerConfig) -> Result<HttpResponse, Error> {
112 let spec_json = config.spec_json()?;
113
114 Ok(HttpResponse::ok()
115 .with_header("content-type".to_string(), "application/json".to_string())
116 .with_body(spec_json.into_bytes()))
117}
118
119pub fn spec_yaml_response(config: &SwaggerConfig) -> Result<HttpResponse, Error> {
121 let spec_yaml = config.spec_yaml()?;
122
123 Ok(HttpResponse::ok()
124 .with_header("content-type".to_string(), "application/x-yaml".to_string())
125 .with_body(spec_yaml.into_bytes()))
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::builder::OpenApiBuilder;
132
133 #[test]
134 fn test_swagger_config() {
135 let spec = OpenApiBuilder::new("Test API", "1.0.0").build();
136 let config = SwaggerConfig::new("/api-docs", spec).with_title("Test Docs");
137
138 assert_eq!(config.path, "/api-docs");
139 assert_eq!(config.title, "Test Docs");
140 }
141
142 #[test]
143 fn test_spec_json() {
144 let spec = OpenApiBuilder::new("Test API", "1.0.0")
145 .description("A test API")
146 .build();
147 let config = SwaggerConfig::new("/api-docs", spec);
148
149 let json = config.spec_json().unwrap();
150 assert!(json.contains("Test API"));
151 assert!(json.contains("1.0.0"));
152 }
153
154 #[test]
155 fn test_spec_yaml() {
156 let spec = OpenApiBuilder::new("Test API", "1.0.0")
157 .description("A test API")
158 .build();
159 let config = SwaggerConfig::new("/api-docs", spec);
160
161 let yaml = config.spec_yaml().unwrap();
162 assert!(yaml.contains("Test API"));
163 assert!(yaml.contains("1.0.0"));
164 }
165}