Skip to main content

nestforge_openapi/
lib.rs

1use axum::{routing::get, Json, Router};
2use serde::Serialize;
3use serde_json::{json, Value};
4
5#[derive(Debug, Clone, Serialize)]
6pub struct OpenApiRoute {
7    pub method: String,
8    pub path: String,
9}
10
11#[derive(Debug, Clone, Serialize)]
12pub struct OpenApiDoc {
13    pub title: String,
14    pub version: String,
15    pub routes: Vec<OpenApiRoute>,
16}
17
18impl OpenApiDoc {
19    pub fn new(title: impl Into<String>, version: impl Into<String>) -> Self {
20        Self {
21            title: title.into(),
22            version: version.into(),
23            routes: Vec::new(),
24        }
25    }
26
27    pub fn add_route(mut self, method: impl Into<String>, path: impl Into<String>) -> Self {
28        self.routes.push(OpenApiRoute {
29            method: method.into(),
30            path: path.into(),
31        });
32        self
33    }
34
35    pub fn to_openapi_json(&self) -> Value {
36        let mut paths = serde_json::Map::new();
37        for route in &self.routes {
38            let method = route.method.to_lowercase();
39            let entry = paths.entry(route.path.clone()).or_insert_with(|| json!({}));
40            let obj = entry.as_object_mut().expect("path entry object");
41            obj.insert(
42                method,
43                json!({
44                    "responses": {
45                        "200": { "description": "OK" }
46                    }
47                }),
48            );
49        }
50
51        json!({
52            "openapi": "3.1.0",
53            "info": {
54                "title": self.title,
55                "version": self.version
56            },
57            "paths": paths
58        })
59    }
60}
61
62pub fn docs_router(doc: OpenApiDoc) -> Router {
63    let openapi = doc.to_openapi_json();
64    let docs_html = r#"<!doctype html>
65<html>
66<head><meta charset="utf-8"><title>NestForge API Docs</title></head>
67<body>
68  <h1>NestForge API Docs</h1>
69  <p>OpenAPI JSON is available at <code>/openapi.json</code>.</p>
70</body>
71</html>"#;
72
73    Router::new()
74        .route(
75            "/openapi.json",
76            get({
77                let payload = openapi.clone();
78                move || async move { Json(payload.clone()) }
79            }),
80        )
81        .route("/docs", get(move || async move { docs_html }))
82}