Skip to main content

nfw_core/openapi/
ui.rs

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}