1use axum::{
2 extract::State,
3 http::StatusCode,
4 response::{Html, IntoResponse, Response},
5 routing::get,
6 Router,
7};
8use std::sync::Arc;
9
10use crate::config::NestForgeWebConfig;
11use crate::openapi::spec::OpenApiSpec;
12
13#[allow(clippy::unwrap_used)]
14pub struct SwaggerUi {
15 spec: OpenApiSpec,
16}
17
18impl SwaggerUi {
19 pub fn new(spec: OpenApiSpec) -> Self {
20 Self { spec }
21 }
22
23 pub fn into_router(self, _config: NestForgeWebConfig) -> Router {
24 let spec = Arc::new(self.spec);
25
26 Router::new()
27 .route("/docs", get(docs_handler))
28 .route("/docs/openapi.json", get(openapi_json_handler))
29 .route("/docs/openapi.yaml", get(openapi_yaml_handler))
30 .with_state(spec)
31 }
32}
33
34async fn docs_handler() -> Html<String> {
35 Html(SWAGGER_HTML.to_string())
36}
37
38async fn openapi_json_handler(State(spec): State<Arc<OpenApiSpec>>) -> impl IntoResponse {
39 let json = serde_json::to_string_pretty(&*spec).unwrap_or_default();
40 Response::builder()
41 .status(StatusCode::OK)
42 .header("Content-Type", "application/json")
43 .body(json)
44 .unwrap_or_else(|_| Response::builder().status(500).body(String::new()).unwrap())
45}
46
47async fn openapi_yaml_handler(State(spec): State<Arc<OpenApiSpec>>) -> impl IntoResponse {
48 let yaml = serde_yaml::to_string(&*spec).unwrap_or_default();
49 Response::builder()
50 .status(StatusCode::OK)
51 .header("Content-Type", "application/x-yaml")
52 .body(yaml)
53 .unwrap_or_else(|_| Response::builder().status(500).body(String::new()).unwrap())
54}
55
56static SWAGGER_HTML: &str = r#"<!DOCTYPE html>
57<html lang="en">
58<head>
59 <meta charset="UTF-8">
60 <meta name="viewport" content="width=device-width, initial-scale=1.0">
61 <title>NestForge Web - API Documentation</title>
62 <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css">
63 <style>
64 body {
65 margin: 0;
66 padding: 0;
67 }
68 </style>
69</head>
70<body>
71 <div id="swagger-ui"></div>
72 <script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
73 <script>
74 window.onload = function() {
75 SwaggerUIBundle({
76 url: "/docs/openapi.json",
77 dom_id: '#swagger-ui',
78 presets: [
79 SwaggerUIBundle.presets.apis,
80 SwaggerUIBundle.SwaggerUIStandalonePreset
81 ],
82 layout: "StandaloneLayout",
83 deepLinking: true,
84 showExtensions: true,
85 showInternals: true,
86 });
87 };
88 </script>
89</body>
90</html>"#;
91
92pub struct Redoc {
93 spec: OpenApiSpec,
94}
95
96impl Redoc {
97 pub fn new(spec: OpenApiSpec) -> Self {
98 Self { spec }
99 }
100
101 pub fn into_router(self, _config: NestForgeWebConfig) -> Router {
102 let spec = Arc::new(self.spec);
103
104 Router::new()
105 .route("/redoc", get(redoc_handler))
106 .route("/docs/openapi.json", get(openapi_json_handler))
107 .with_state(spec)
108 }
109}
110
111async fn redoc_handler() -> Html<String> {
112 Html(
113 r#"<!DOCTYPE html>
114<html lang="en">
115<head>
116 <meta charset="UTF-8">
117 <meta name="viewport" content="width=device-width, initial-scale=1.0">
118 <title>NestForge Web - API Documentation</title>
119 <link rel="stylesheet" href="https://unpkg.com/redoc@latest/bundles/redoc.standalone.css">
120</head>
121<body>
122 <redoc spec-url="/docs/openapi.json"></redoc>
123 <script src="https://unpkg.com/redoc@latest/bundles/redoc.standalone.js"></script>
124</body>
125</html>"#
126 .to_string(),
127 )
128}