Skip to main content

openapi_ui/
openapi.rs

1//! Types matching the OpenAPI 3.x specification.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Root document object of the OpenAPI specification.
7#[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/// Metadata about the API.
26#[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/// Contact information for the API.
43#[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/// License information for the API.
54#[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/// An object representing a server.
62#[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/// Describes the operations available on a single path.
70#[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/// Describes a single API operation on a path.
100#[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/// Describes a single operation parameter.
129#[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/// The location of a parameter (query, header, path, or cookie).
149#[derive(Debug, Clone, Serialize, Deserialize)]
150#[serde(rename_all = "lowercase")]
151pub enum ParameterLocation {
152    Query,
153    Header,
154    Path,
155    Cookie,
156}
157
158/// Describes a request body.
159#[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/// Describes a media type with optional schema and examples.
169#[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/// A single encoding definition applied to a single schema property.
182#[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/// Describes a single header parameter.
197#[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/// Describes a single response from an API operation.
212#[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/// An example value for a parameter, media type, or schema.
224#[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/// The Schema Object allows the definition of input and output data types.
237#[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/// Discriminator object for polymorphism support.
314#[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/// XML metadata for a schema property.
322#[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/// Holds reusable schema, response, parameter, and other objects.
337#[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/// Defines a security scheme that can be used by the operations.
360#[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/// HTTP authentication scheme (e.g., `bearer`, `basic`).
390#[derive(Debug, Clone, Serialize, Deserialize)]
391#[serde(untagged)]
392pub enum HttpScheme {
393    String(String),
394    /// For handling array or other formats from utoipa
395    Flexible(serde_json::Value),
396}
397
398/// Schema type, which can be a single string or an array of strings.
399#[derive(Debug, Clone, Serialize, Deserialize)]
400#[serde(untagged)]
401pub enum SchemaType {
402    String(String),
403    Array(Vec<String>),
404}
405
406/// The location of an API key parameter.
407#[derive(Debug, Clone, Serialize, Deserialize)]
408#[serde(rename_all = "lowercase")]
409pub enum ApiKeyLocation {
410    Query,
411    Header,
412    Cookie,
413}
414
415/// A security requirement object listing the required security schemes.
416#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct SecurityRequirement(pub HashMap<String, Vec<String>>);
418
419/// Adds metadata to a single tag used by operations.
420#[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/// Allows referencing an external resource for extended documentation.
430#[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}