Skip to main content

oxihuman_export/
openapi_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! OpenAPI 3.0 schema stub export (JSON).
6
7/// An OpenAPI 3.0 info block.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct OpenApiInfo {
11    pub title: String,
12    pub version: String,
13    pub description: String,
14}
15
16/// An OpenAPI parameter.
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct OpenApiParam {
20    pub name: String,
21    pub r#in: String,
22    pub required: bool,
23    pub schema_type: String,
24}
25
26/// An OpenAPI path item.
27#[allow(dead_code)]
28#[derive(Debug, Clone)]
29pub struct OpenApiPath {
30    pub path: String,
31    pub method: String,
32    pub summary: String,
33    pub parameters: Vec<OpenApiParam>,
34    pub response_schema: String,
35}
36
37/// An OpenAPI 3.0 document.
38#[allow(dead_code)]
39#[derive(Debug, Clone)]
40pub struct OpenApiDoc {
41    pub info: OpenApiInfo,
42    pub paths: Vec<OpenApiPath>,
43}
44
45/// Create a new OpenAPI document.
46#[allow(dead_code)]
47pub fn new_openapi_doc(title: &str, version: &str) -> OpenApiDoc {
48    OpenApiDoc {
49        info: OpenApiInfo {
50            title: title.to_string(),
51            version: version.to_string(),
52            description: String::new(),
53        },
54        paths: Vec::new(),
55    }
56}
57
58/// Add a path to the document.
59#[allow(dead_code)]
60pub fn add_path(doc: &mut OpenApiDoc, path: &str, method: &str, summary: &str) -> usize {
61    doc.paths.push(OpenApiPath {
62        path: path.to_string(),
63        method: method.to_string(),
64        summary: summary.to_string(),
65        parameters: Vec::new(),
66        response_schema: "object".to_string(),
67    });
68    doc.paths.len() - 1
69}
70
71/// Add a parameter to a path.
72#[allow(dead_code)]
73pub fn add_param(
74    doc: &mut OpenApiDoc,
75    path_idx: usize,
76    name: &str,
77    location: &str,
78    required: bool,
79    schema_type: &str,
80) {
81    if path_idx < doc.paths.len() {
82        doc.paths[path_idx].parameters.push(OpenApiParam {
83            name: name.to_string(),
84            r#in: location.to_string(),
85            required,
86            schema_type: schema_type.to_string(),
87        });
88    }
89}
90
91/// Export to JSON text.
92#[allow(dead_code)]
93pub fn export_openapi_json(doc: &OpenApiDoc) -> String {
94    let mut paths_json = String::new();
95    for (i, p) in doc.paths.iter().enumerate() {
96        let params: Vec<String> = p
97            .parameters
98            .iter()
99            .map(|param| {
100                format!(
101                    r#"{{"name":"{}","in":"{}","required":{},"schema":{{"type":"{}"}}}}"#,
102                    param.name, param.r#in, param.required, param.schema_type
103                )
104            })
105            .collect();
106        let params_arr = format!("[{}]", params.join(","));
107        let path_entry = format!(
108            r#""{}":{{"{}": {{"summary":"{}","parameters":{},"responses":{{"200":{{"description":"OK"}}}}}}}}"#,
109            p.path, p.method, p.summary, params_arr
110        );
111        if i > 0 {
112            paths_json.push(',');
113        }
114        paths_json.push_str(&path_entry);
115    }
116    format!(
117        r#"{{"openapi":"3.0.0","info":{{"title":"{}","version":"{}"}},"paths":{{{}}}}}"#,
118        doc.info.title, doc.info.version, paths_json
119    )
120}
121
122/// Path count.
123#[allow(dead_code)]
124pub fn path_count(doc: &OpenApiDoc) -> usize {
125    doc.paths.len()
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn new_doc_title() {
134        let doc = new_openapi_doc("My API", "1.0");
135        assert_eq!(doc.info.title, "My API");
136    }
137
138    #[test]
139    fn add_path_increases_count() {
140        let mut doc = new_openapi_doc("API", "1.0");
141        add_path(&mut doc, "/users", "get", "List users");
142        assert_eq!(path_count(&doc), 1);
143    }
144
145    #[test]
146    fn export_contains_openapi_version() {
147        let doc = new_openapi_doc("API", "1.0");
148        let s = export_openapi_json(&doc);
149        assert!(s.contains("3.0.0"));
150    }
151
152    #[test]
153    fn export_contains_title() {
154        let doc = new_openapi_doc("My API", "1.0");
155        let s = export_openapi_json(&doc);
156        assert!(s.contains("My API"));
157    }
158
159    #[test]
160    fn export_contains_path() {
161        let mut doc = new_openapi_doc("API", "1.0");
162        add_path(&mut doc, "/users", "get", "Get users");
163        let s = export_openapi_json(&doc);
164        assert!(s.contains("/users"));
165    }
166
167    #[test]
168    fn add_param_to_path() {
169        let mut doc = new_openapi_doc("API", "1.0");
170        let idx = add_path(&mut doc, "/user", "get", "Get user");
171        add_param(&mut doc, idx, "id", "query", true, "integer");
172        assert_eq!(doc.paths[0].parameters.len(), 1);
173    }
174
175    #[test]
176    fn param_in_export() {
177        let mut doc = new_openapi_doc("API", "1.0");
178        let idx = add_path(&mut doc, "/user", "get", "Get user");
179        add_param(&mut doc, idx, "id", "query", true, "integer");
180        let s = export_openapi_json(&doc);
181        assert!(s.contains("\"id\""));
182    }
183
184    #[test]
185    fn invalid_path_idx_ignored() {
186        let mut doc = new_openapi_doc("API", "1.0");
187        add_param(&mut doc, 99, "x", "query", false, "string");
188        assert_eq!(path_count(&doc), 0);
189    }
190
191    #[test]
192    fn empty_paths_json() {
193        let doc = new_openapi_doc("API", "1.0");
194        let s = export_openapi_json(&doc);
195        assert!(s.contains("paths"));
196    }
197
198    #[test]
199    fn version_in_export() {
200        let doc = new_openapi_doc("API", "2.5.0");
201        let s = export_openapi_json(&doc);
202        assert!(s.contains("2.5.0"));
203    }
204}