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