oxihuman_export/
openapi_schema_export.rs1#![allow(dead_code)]
4
5use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
11pub enum HttpMethod {
12 Get,
13 Post,
14 Put,
15 Delete,
16 Patch,
17}
18
19impl HttpMethod {
20 pub fn as_str(&self) -> &'static str {
22 match self {
23 Self::Get => "get",
24 Self::Post => "post",
25 Self::Put => "put",
26 Self::Delete => "delete",
27 Self::Patch => "patch",
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct ApiOperation {
35 pub method: HttpMethod,
36 pub summary: String,
37 pub operation_id: String,
38 pub tags: Vec<String>,
39 pub response_codes: Vec<u16>,
40}
41
42#[derive(Debug, Clone, Default)]
44pub struct ApiPath {
45 pub operations: BTreeMap<String, ApiOperation>,
46}
47
48impl ApiPath {
49 pub fn add_operation(&mut self, method: HttpMethod, op: ApiOperation) {
51 self.operations.insert(method.as_str().to_string(), op);
52 }
53
54 pub fn operation_count(&self) -> usize {
56 self.operations.len()
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct ApiInfo {
63 pub title: String,
64 pub version: String,
65 pub description: Option<String>,
66}
67
68#[derive(Debug, Clone, Default)]
70pub struct OpenApiSpec {
71 pub info: Option<ApiInfo>,
72 pub paths: BTreeMap<String, ApiPath>,
73 pub servers: Vec<String>,
74}
75
76impl OpenApiSpec {
77 pub fn add_path(&mut self, path: impl Into<String>, item: ApiPath) {
79 self.paths.insert(path.into(), item);
80 }
81
82 pub fn path_count(&self) -> usize {
84 self.paths.len()
85 }
86
87 pub fn find_path(&self, path: &str) -> Option<&ApiPath> {
89 self.paths.get(path)
90 }
91}
92
93pub fn render_openapi_json(spec: &OpenApiSpec) -> String {
95 let title = spec
96 .info
97 .as_ref()
98 .map(|i| i.title.as_str())
99 .unwrap_or("API");
100 let version = spec
101 .info
102 .as_ref()
103 .map(|i| i.version.as_str())
104 .unwrap_or("1.0.0");
105 let paths_json: Vec<String> = spec
106 .paths
107 .iter()
108 .map(|(path, item)| {
109 let ops: Vec<String> = item
110 .operations
111 .iter()
112 .map(|(method, op)| {
113 format!(
114 r#""{method}":{{"summary":"{}","operationId":"{}"}}"#,
115 op.summary, op.operation_id
116 )
117 })
118 .collect();
119 format!(r#""{path}":{{{}}} "#, ops.join(","))
120 })
121 .collect();
122 format!(
123 r#"{{"openapi":"3.0.0","info":{{"title":"{title}","version":"{version}"}},"paths":{{{}}}}}"#,
124 paths_json.join(",")
125 )
126}
127
128pub fn validate_spec(spec: &OpenApiSpec) -> bool {
130 spec.info.is_some() && !spec.paths.is_empty()
131}
132
133pub fn total_operation_count(spec: &OpenApiSpec) -> usize {
135 spec.paths.values().map(|p| p.operation_count()).sum()
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 fn sample_spec() -> OpenApiSpec {
143 let mut spec = OpenApiSpec {
144 info: Some(ApiInfo {
145 title: "Test API".into(),
146 version: "1.0.0".into(),
147 description: None,
148 }),
149 ..Default::default()
150 };
151 let mut path = ApiPath::default();
152 path.add_operation(
153 HttpMethod::Get,
154 ApiOperation {
155 method: HttpMethod::Get,
156 summary: "List things".into(),
157 operation_id: "listThings".into(),
158 tags: vec!["things".into()],
159 response_codes: vec![200],
160 },
161 );
162 spec.add_path("/things", path);
163 spec
164 }
165
166 #[test]
167 fn path_count() {
168 assert_eq!(sample_spec().path_count(), 1);
169 }
170
171 #[test]
172 fn find_path_found() {
173 assert!(sample_spec().find_path("/things").is_some());
174 }
175
176 #[test]
177 fn operation_count() {
178 let spec = sample_spec();
179 let p = spec.find_path("/things").expect("should succeed");
180 assert_eq!(p.operation_count(), 1);
181 }
182
183 #[test]
184 fn render_contains_openapi_version() {
185 assert!(render_openapi_json(&sample_spec()).contains("3.0.0"));
186 }
187
188 #[test]
189 fn render_contains_title() {
190 assert!(render_openapi_json(&sample_spec()).contains("Test API"));
191 }
192
193 #[test]
194 fn validate_ok() {
195 assert!(validate_spec(&sample_spec()));
196 }
197
198 #[test]
199 fn validate_no_info() {
200 let spec = OpenApiSpec::default();
201 assert!(!validate_spec(&spec));
202 }
203
204 #[test]
205 fn total_operations() {
206 assert_eq!(total_operation_count(&sample_spec()), 1);
207 }
208
209 #[test]
210 fn method_as_str() {
211 assert_eq!(HttpMethod::Post.as_str(), "post");
212 }
213}