Skip to main content

openapi_ui/
openapi.rs

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    /// For handling array or other formats from utoipa
372    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}