1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct OpenAPISpec {
6 pub openapi: String,
7 pub info: Info,
8 #[serde(default)]
9 pub servers: Vec<Server>,
10 #[serde(default)]
11 pub paths: HashMap<String, PathItem>,
12 #[serde(default, skip_serializing_if = "Option::is_none")]
13 pub components: Option<Components>,
14 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub security: Option<Vec<SecurityRequirement>>,
16 #[serde(default, skip_serializing_if = "Option::is_none")]
17 pub tags: Option<Vec<Tag>>,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub external_docs: Option<ExternalDocumentation>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Info {
24 pub title: String,
25 pub version: String,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub description: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub terms_of_service: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub contact: Option<Contact>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub license: Option<License>,
34 #[serde(skip_serializing_if = "Option::is_none", rename = "x-logo")]
35 pub x_logo: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Contact {
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub name: Option<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub url: Option<String>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub email: Option<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct License {
50 pub name: String,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub url: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct Server {
57 pub url: String,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub description: Option<String>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "lowercase")]
64pub struct PathItem {
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub get: Option<Operation>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub put: Option<Operation>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub post: Option<Operation>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub delete: Option<Operation>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub options: Option<Operation>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub head: Option<Operation>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub patch: Option<Operation>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub trace: Option<Operation>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub servers: Option<Vec<Server>>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub parameters: Option<Vec<Parameter>>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub summary: Option<String>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub description: Option<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct Operation {
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub tags: Option<Vec<String>>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub summary: Option<String>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub description: Option<String>,
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub external_docs: Option<ExternalDocumentation>,
101 #[serde(skip_serializing_if = "Option::is_none", rename = "operationId")]
102 pub operation_id: Option<String>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub parameters: Option<Vec<Parameter>>,
105 #[serde(skip_serializing_if = "Option::is_none", rename = "requestBody")]
106 pub request_body: Option<RequestBody>,
107 #[serde(default)]
108 pub responses: HashMap<String, Response>,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub callbacks: Option<HashMap<String, serde_json::Value>>,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub deprecated: Option<bool>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub security: Option<Vec<SecurityRequirement>>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub servers: Option<Vec<Server>>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct Parameter {
121 pub name: String,
122 #[serde(rename = "in")]
123 pub parameter_in: ParameterLocation,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub description: Option<String>,
126 #[serde(default)]
127 pub required: bool,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub deprecated: Option<bool>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub schema: Option<Schema>,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub example: Option<serde_json::Value>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub examples: Option<HashMap<String, Example>>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139#[serde(rename_all = "lowercase")]
140pub enum ParameterLocation {
141 Query,
142 Header,
143 Path,
144 Cookie,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct RequestBody {
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub description: Option<String>,
151 pub content: HashMap<String, MediaType>,
152 #[serde(default)]
153 pub required: bool,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct MediaType {
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub schema: Option<Schema>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub example: Option<serde_json::Value>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub examples: Option<HashMap<String, Example>>,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub encoding: Option<HashMap<String, Encoding>>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct Encoding {
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub content_type: Option<String>,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub headers: Option<HashMap<String, Header>>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub style: Option<String>,
176 #[serde(default)]
177 pub explode: bool,
178 #[serde(default)]
179 pub allow_reserved: bool,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct Header {
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub description: Option<String>,
186 #[serde(default)]
187 pub required: bool,
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub deprecated: Option<bool>,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub schema: Option<Schema>,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub example: Option<serde_json::Value>,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct Response {
198 pub description: String,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub headers: Option<HashMap<String, Header>>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub content: Option<HashMap<String, MediaType>>,
203 #[serde(skip_serializing_if = "Option::is_none")]
204 pub links: Option<HashMap<String, serde_json::Value>>,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct Example {
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub summary: Option<String>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub description: Option<String>,
213 #[serde(skip_serializing_if = "Option::is_none")]
214 pub value: Option<serde_json::Value>,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub external_value: Option<String>,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct Schema {
221 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
222 pub reference: Option<String>,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub r#type: Option<SchemaType>,
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub format: Option<String>,
227 #[serde(skip_serializing_if = "Option::is_none")]
228 pub title: Option<String>,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub description: Option<String>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub default: Option<serde_json::Value>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub multiple_of: Option<f64>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub maximum: Option<f64>,
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub exclusive_maximum: Option<bool>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub minimum: Option<f64>,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub exclusive_minimum: Option<bool>,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub max_length: Option<u64>,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub min_length: Option<u64>,
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub pattern: Option<String>,
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub max_items: Option<u64>,
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub min_items: Option<u64>,
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub unique_items: Option<bool>,
255 #[serde(skip_serializing_if = "Option::is_none")]
256 pub max_properties: Option<u64>,
257 #[serde(skip_serializing_if = "Option::is_none")]
258 pub min_properties: Option<u64>,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub required: Option<Vec<String>>,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub r#enum: Option<Vec<serde_json::Value>>,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub items: Option<Box<Schema>>,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub properties: Option<HashMap<String, Schema>>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub additional_properties: Option<serde_json::Value>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub nullable: Option<bool>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 pub discriminator: Option<Discriminator>,
273 #[serde(skip_serializing_if = "Option::is_none")]
274 pub read_only: Option<bool>,
275 #[serde(skip_serializing_if = "Option::is_none")]
276 pub write_only: Option<bool>,
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub xml: Option<Xml>,
279 #[serde(skip_serializing_if = "Option::is_none")]
280 pub external_docs: Option<ExternalDocumentation>,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub example: Option<serde_json::Value>,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub deprecated: Option<bool>,
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub all_of: Option<Vec<Schema>>,
287 #[serde(skip_serializing_if = "Option::is_none")]
288 pub one_of: Option<Vec<Schema>>,
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub any_of: Option<Vec<Schema>>,
291 #[serde(skip_serializing_if = "Option::is_none")]
292 pub not: Option<Box<Schema>>,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct Discriminator {
297 pub property_name: String,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub mapping: Option<HashMap<String, String>>,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct Xml {
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub name: Option<String>,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 pub namespace: Option<String>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub prefix: Option<String>,
310 #[serde(default)]
311 pub attribute: bool,
312 #[serde(default)]
313 pub wrapped: bool,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct Components {
318 #[serde(skip_serializing_if = "Option::is_none")]
319 pub schemas: Option<HashMap<String, Schema>>,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub responses: Option<HashMap<String, Response>>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 pub parameters: Option<HashMap<String, Parameter>>,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 pub examples: Option<HashMap<String, Example>>,
326 #[serde(skip_serializing_if = "Option::is_none")]
327 pub request_bodies: Option<HashMap<String, RequestBody>>,
328 #[serde(skip_serializing_if = "Option::is_none")]
329 pub headers: Option<HashMap<String, Header>>,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub security_schemes: Option<HashMap<String, SecurityScheme>>,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub links: Option<HashMap<String, serde_json::Value>>,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 pub callbacks: Option<HashMap<String, serde_json::Value>>,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
339#[serde(tag = "type", rename_all = "lowercase")]
340pub enum SecurityScheme {
341 ApiKey {
342 #[serde(rename = "in")]
343 location: ApiKeyLocation,
344 name: String,
345 #[serde(skip_serializing_if = "Option::is_none")]
346 description: Option<String>,
347 },
348 Http {
349 scheme: HttpScheme,
350 #[serde(skip_serializing_if = "Option::is_none")]
351 bearer_format: Option<String>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 description: Option<String>,
354 },
355 Oauth2 {
356 flows: serde_json::Value,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 description: Option<String>,
359 },
360 OpenIdConnect {
361 open_id_connect_url: String,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 description: Option<String>,
364 },
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
368#[serde(untagged)]
369pub enum HttpScheme {
370 String(String),
371 Flexible(serde_json::Value),
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
376#[serde(untagged)]
377pub enum SchemaType {
378 String(String),
379 Array(Vec<String>),
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
383#[serde(rename_all = "lowercase")]
384pub enum ApiKeyLocation {
385 Query,
386 Header,
387 Cookie,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
391pub struct SecurityRequirement(pub HashMap<String, Vec<String>>);
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct Tag {
395 pub name: String,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub description: Option<String>,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub external_docs: Option<ExternalDocumentation>,
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct ExternalDocumentation {
404 #[serde(skip_serializing_if = "Option::is_none")]
405 pub description: Option<String>,
406 pub url: String,
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[test]
414 fn test_parse_sample_openapi() {
415 let json = r#"{
416 "openapi": "3.0.0",
417 "info": {
418 "title": "Test API",
419 "version": "1.0.0",
420 "description": "A test API"
421 },
422 "paths": {}
423 }"#;
424
425 let spec: Result<OpenAPISpec, _> = serde_json::from_str(json);
426 assert!(spec.is_ok());
427 let spec = spec.unwrap();
428 assert_eq!(spec.openapi, "3.0.0");
429 assert_eq!(spec.info.title, "Test API");
430 assert_eq!(spec.info.version, "1.0.0");
431 }
432}