oas_gen/
okapi3.rs

1// This file was built upon the OpenApi struct of okapi
2// https://github.com/GREsau/okapi/blob/ea2ae0cc25d56b9bbba644e957ceada615d2b01d/okapi/src/openapi3.rs
3// Acquired under the MIT
4
5use http::Method;
6use schemars::gen::SchemaGenerator;
7use schemars::schema::SchemaObject;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::{hash_map::Entry as HashEntry, HashMap};
11use std::iter::FromIterator;
12
13pub type Map<K, V> = schemars::Map<K, V>;
14
15type Object = Map<String, Value>;
16pub type SecurityRequirement = Map<String, Vec<String>>;
17
18#[derive(Debug, Clone)]
19pub struct OpenApiGenerator {
20    pub schema_generator: SchemaGenerator,
21    pub operations: HashMap<(String, http::Method), Operation>,
22}
23impl OpenApiGenerator {
24    pub fn new(generator: SchemaGenerator) -> Self {
25        OpenApiGenerator {
26            schema_generator: generator,
27            operations: std::collections::HashMap::default(),
28        }
29    }
30    pub fn add_operation(&mut self, mut op: OperationInfo) {
31        if let Some(op_id) = op.operation.operation_id {
32            // TODO do this outside add_operation
33            op.operation.operation_id = Some(op_id.trim_start_matches(':').replace("::", "_"));
34        }
35        match self.operations.entry((op.path, op.method)) {
36            HashEntry::Occupied(e) => {
37                let (path, method) = e.key();
38                panic!(
39                    "An OpenAPI operation has already been added for {} {}",
40                    method, path
41                );
42            }
43            HashEntry::Vacant(e) => e.insert(op.operation),
44        };
45    }
46
47    pub fn into_openapi(self) -> OpenApi {
48        OpenApi {
49            openapi: "3.0.0".to_owned(),
50            paths: {
51                let mut paths = Map::new();
52                for ((path, method), op) in self.operations {
53                    let path_item: &mut PathItem = paths.entry(path).or_default();
54                    Self::set_operation(path_item, &method, op);
55                }
56                paths
57            },
58            components: Some(Components {
59                schemas: Map::from_iter(
60                    self.schema_generator
61                        .clone()
62                        .take_definitions()
63                        .into_iter()
64                        .map(|(k, v)| (k, v.into())),
65                ),
66                ..Components::default()
67            }),
68            ..OpenApi::default()
69        }
70    }
71
72    fn set_operation(path_item: &mut PathItem, method: &http::Method, op: Operation) {
73        // use http::Method::*;
74        let option = match *method {
75            Method::GET => &mut path_item.get,
76            Method::PUT => &mut path_item.put,
77            Method::POST => &mut path_item.post,
78            Method::DELETE => &mut path_item.delete,
79            Method::OPTIONS => &mut path_item.options,
80            Method::HEAD => &mut path_item.head,
81            Method::PATCH => &mut path_item.patch,
82            Method::TRACE => &mut path_item.trace,
83            // Connect not available in OpenAPI3. Maybe should set in extensions?
84            // &Method::CONNECT => return,
85            _ => return,
86        };
87        assert!(option.is_none());
88        option.replace(op);
89    }
90}
91
92pub struct OperationInfo {
93    pub path: String,
94    pub method: http::Method,
95    pub operation: Operation,
96}
97
98#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
99#[serde(untagged)]
100pub enum RefOr<T> {
101    Ref(Ref),
102    Object(T),
103}
104
105#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
106
107pub struct Ref {
108    #[serde(rename = "$ref")]
109    pub reference: String,
110}
111
112impl<T> From<T> for RefOr<T> {
113    fn from(o: T) -> Self {
114        RefOr::<T>::Object(o)
115    }
116}
117
118#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
119#[serde(rename_all = "camelCase")]
120pub struct OpenApi {
121    pub openapi: String,
122    pub info: Info,
123    #[serde(default, skip_serializing_if = "Vec::is_empty")]
124    pub servers: Vec<Server>,
125    pub paths: Map<String, PathItem>,
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub components: Option<Components>,
128    #[serde(default, skip_serializing_if = "Vec::is_empty")]
129    pub security: Vec<SecurityRequirement>,
130    #[serde(default, skip_serializing_if = "Vec::is_empty")]
131    pub tags: Vec<Tag>,
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub external_docs: Option<ExternalDocs>,
134    #[serde(flatten)]
135    pub extensions: Object,
136}
137
138#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
139#[serde(rename_all = "camelCase")]
140pub struct Info {
141    pub title: String,
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub description: Option<String>,
144    #[serde(default, skip_serializing_if = "Option::is_none")]
145    pub terms_of_service: Option<String>,
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    pub contact: Option<Contact>,
148    #[serde(default, skip_serializing_if = "Option::is_none")]
149    pub license: Option<License>,
150    pub version: String,
151    #[serde(flatten)]
152    pub extensions: Object,
153}
154
155#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
156#[serde(default, rename_all = "camelCase")]
157pub struct Contact {
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub name: Option<String>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub url: Option<String>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub email: Option<String>,
164    #[serde(flatten)]
165    pub extensions: Object,
166}
167
168#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
169#[serde(rename_all = "camelCase")]
170pub struct License {
171    pub name: String,
172    #[serde(default, skip_serializing_if = "Option::is_none")]
173    pub url: Option<String>,
174    #[serde(flatten)]
175    pub extensions: Object,
176}
177
178#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
179#[serde(rename_all = "camelCase")]
180pub struct Server {
181    pub url: String,
182    #[serde(default, skip_serializing_if = "Option::is_none")]
183    pub description: Option<String>,
184    #[serde(default, skip_serializing_if = "Map::is_empty")]
185    pub variables: Map<String, ServerVariable>,
186    #[serde(flatten)]
187    pub extensions: Object,
188}
189
190#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
191#[serde(rename_all = "camelCase")]
192pub struct ServerVariable {
193    #[serde(default, rename = "enum", skip_serializing_if = "Option::is_none")]
194    pub enumeration: Option<Vec<String>>,
195    pub default: String,
196    #[serde(default, skip_serializing_if = "Option::is_none")]
197    pub description: Option<String>,
198    #[serde(flatten)]
199    pub extensions: Object,
200}
201
202#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
203#[serde(default, rename_all = "camelCase")]
204pub struct PathItem {
205    #[serde(default, rename = "$ref", skip_serializing_if = "Option::is_none")]
206    pub reference: Option<String>,
207    #[serde(default, skip_serializing_if = "Option::is_none")]
208    pub summary: Option<String>,
209    #[serde(default, skip_serializing_if = "Option::is_none")]
210    pub description: Option<String>,
211    #[serde(default, skip_serializing_if = "Option::is_none")]
212    pub get: Option<Operation>,
213    #[serde(default, skip_serializing_if = "Option::is_none")]
214    pub put: Option<Operation>,
215    #[serde(default, skip_serializing_if = "Option::is_none")]
216    pub post: Option<Operation>,
217    #[serde(default, skip_serializing_if = "Option::is_none")]
218    pub delete: Option<Operation>,
219    #[serde(default, skip_serializing_if = "Option::is_none")]
220    pub options: Option<Operation>,
221    #[serde(default, skip_serializing_if = "Option::is_none")]
222    pub head: Option<Operation>,
223    #[serde(default, skip_serializing_if = "Option::is_none")]
224    pub patch: Option<Operation>,
225    #[serde(default, skip_serializing_if = "Option::is_none")]
226    pub trace: Option<Operation>,
227    #[serde(default, skip_serializing_if = "Option::is_none")]
228    pub servers: Option<Vec<Server>>,
229    #[serde(default, skip_serializing_if = "Vec::is_empty")]
230    pub parameters: Vec<RefOr<Parameter>>,
231    #[serde(flatten)]
232    pub extensions: Object,
233}
234
235#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
236#[serde(rename_all = "camelCase")]
237pub struct Operation {
238    #[serde(default, skip_serializing_if = "Vec::is_empty")]
239    pub tags: Vec<String>,
240    #[serde(default, skip_serializing_if = "Option::is_none")]
241    pub summary: Option<String>,
242    #[serde(default, skip_serializing_if = "Option::is_none")]
243    pub description: Option<String>,
244    #[serde(default, skip_serializing_if = "Option::is_none")]
245    pub external_docs: Option<ExternalDocs>,
246    #[serde(default, skip_serializing_if = "Option::is_none")]
247    pub operation_id: Option<String>,
248    #[serde(default, skip_serializing_if = "Vec::is_empty")]
249    pub parameters: Vec<RefOr<Parameter>>,
250    #[serde(default, skip_serializing_if = "Option::is_none")]
251    pub request_body: Option<RefOr<RequestBody>>,
252    pub responses: Responses,
253    #[serde(default, skip_serializing_if = "Map::is_empty")]
254    pub callbacks: Map<String, RefOr<Callback>>,
255    #[serde(default, skip_serializing_if = "is_false")]
256    pub deprecated: bool,
257    #[serde(default, skip_serializing_if = "Option::is_none")]
258    pub security: Option<Vec<SecurityRequirement>>,
259    #[serde(default, skip_serializing_if = "Option::is_none")]
260    pub servers: Option<Vec<Server>>,
261    #[serde(flatten)]
262    pub extensions: Object,
263}
264
265#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
266#[serde(default, rename_all = "camelCase")]
267pub struct Responses {
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub default: Option<RefOr<Response>>,
270    #[serde(flatten)]
271    pub responses: Map<String, RefOr<Response>>,
272    #[serde(flatten)]
273    pub extensions: Object,
274}
275
276#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
277#[serde(default, rename_all = "camelCase")]
278pub struct Components {
279    #[serde(default, skip_serializing_if = "Map::is_empty")]
280    pub schemas: Map<String, SchemaObject>,
281    #[serde(default, skip_serializing_if = "Map::is_empty")]
282    pub responses: Map<String, RefOr<Response>>,
283    #[serde(default, skip_serializing_if = "Map::is_empty")]
284    pub parameters: Map<String, RefOr<Parameter>>,
285    #[serde(default, skip_serializing_if = "Map::is_empty")]
286    pub examples: Map<String, RefOr<Example>>,
287    #[serde(default, skip_serializing_if = "Map::is_empty")]
288    pub request_bodies: Map<String, RefOr<RequestBody>>,
289    #[serde(default, skip_serializing_if = "Map::is_empty")]
290    pub headers: Map<String, RefOr<Header>>,
291    #[serde(default, skip_serializing_if = "Map::is_empty")]
292    pub security_schemes: Map<String, RefOr<SecurityScheme>>,
293    #[serde(default, skip_serializing_if = "Map::is_empty")]
294    pub links: Map<String, RefOr<Link>>,
295    #[serde(default, skip_serializing_if = "Map::is_empty")]
296    pub callbacks: Map<String, RefOr<Callback>>,
297    #[serde(flatten)]
298    pub extensions: Object,
299}
300
301#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
302#[serde(rename_all = "camelCase")]
303pub struct Response {
304    pub description: String,
305    #[serde(default, skip_serializing_if = "Map::is_empty")]
306    pub headers: Map<String, RefOr<Header>>,
307    #[serde(default, skip_serializing_if = "Map::is_empty")]
308    pub content: Map<String, MediaType>,
309    #[serde(default, skip_serializing_if = "Map::is_empty")]
310    pub links: Map<String, RefOr<Link>>,
311    #[serde(flatten)]
312    pub extensions: Object,
313}
314
315#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
316#[serde(rename_all = "camelCase")]
317pub struct Parameter {
318    pub name: String,
319    // TODO this should probaby be an enum, not String
320    #[serde(rename = "in")]
321    pub location: String,
322    #[serde(default, skip_serializing_if = "Option::is_none")]
323    pub description: Option<String>,
324    #[serde(default, skip_serializing_if = "is_false")]
325    pub required: bool,
326    #[serde(default, skip_serializing_if = "is_false")]
327    pub deprecated: bool,
328    #[serde(default)]
329    pub allow_empty_value: bool,
330    #[serde(flatten)]
331    pub value: ParameterValue,
332    #[serde(flatten)]
333    pub extensions: Object,
334}
335
336// maybe this should just been inlined into Parameter as fields?
337#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
338#[serde(untagged, rename_all = "camelCase")]
339pub enum ParameterValue {
340    Schema {
341        #[serde(default, skip_serializing_if = "Option::is_none")]
342        style: Option<ParameterStyle>,
343        #[serde(default, skip_serializing_if = "Option::is_none")]
344        explode: Option<bool>,
345        #[serde(default, skip_serializing_if = "is_false")]
346        allow_reserved: bool,
347        schema: Box<SchemaObject>,
348        #[serde(default, skip_serializing_if = "Option::is_none")]
349        example: Option<Value>,
350        #[serde(default, skip_serializing_if = "Option::is_none")]
351        examples: Option<Map<String, Example>>,
352    },
353    Content {
354        content: Map<String, MediaType>,
355    },
356}
357
358#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
359#[serde(rename_all = "camelCase")]
360pub enum ParameterStyle {
361    Matrix,
362    Label,
363    Form,
364    Simple,
365    SpaceDelimited,
366    PipeDelimited,
367    DeepObject,
368}
369
370#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
371#[serde(rename_all = "camelCase")]
372pub struct Example {
373    #[serde(default, skip_serializing_if = "Option::is_none")]
374    pub summary: Option<String>,
375    #[serde(default, skip_serializing_if = "Option::is_none")]
376    pub description: Option<String>,
377    #[serde(flatten)]
378    pub value: ExampleValue,
379    #[serde(flatten)]
380    pub extensions: Object,
381}
382
383#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
384#[serde(rename_all = "camelCase")]
385pub enum ExampleValue {
386    Value(Value),
387    ExternalValue(String),
388}
389
390#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
391#[serde(rename_all = "camelCase")]
392pub struct RequestBody {
393    #[serde(default, skip_serializing_if = "Option::is_none")]
394    pub description: Option<String>,
395    pub content: Map<String, MediaType>,
396    #[serde(default, skip_serializing_if = "is_false")]
397    pub required: bool,
398    #[serde(flatten)]
399    pub extensions: Object,
400}
401
402#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
403#[serde(rename_all = "camelCase")]
404pub struct Header {
405    #[serde(default, skip_serializing_if = "Option::is_none")]
406    pub description: Option<String>,
407    #[serde(default, skip_serializing_if = "is_false")]
408    pub required: bool,
409    #[serde(default, skip_serializing_if = "is_false")]
410    pub deprecated: bool,
411    #[serde(default, skip_serializing_if = "is_false")]
412    pub allow_empty_value: bool,
413    #[serde(flatten)]
414    pub value: ParameterValue,
415    #[serde(flatten)]
416    pub extensions: Object,
417}
418
419#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
420#[serde(rename_all = "camelCase")]
421pub struct SecurityScheme {
422    #[serde(rename = "type")]
423    #[serde(default, skip_serializing_if = "Option::is_none")]
424    pub schema_type: Option<String>,
425    #[serde(default, skip_serializing_if = "Option::is_none")]
426    pub description: Option<String>,
427    #[serde(flatten)]
428    pub data: SecuritySchemeData,
429    #[serde(flatten)]
430    pub extensions: Object,
431}
432
433#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
434#[serde(tag = "type", rename_all = "camelCase")]
435pub enum SecuritySchemeData {
436    ApiKey {
437        name: String,
438        #[serde(rename = "in")]
439        location: String,
440    },
441    Http {
442        scheme: String,
443        #[serde(rename = "bearerFormat")]
444        #[serde(default, skip_serializing_if = "Option::is_none")]
445        bearer_format: Option<String>,
446    },
447    #[serde(rename = "oauth2")]
448    OAuth2 {
449        flows: Box<OAuthFlows>,
450    },
451    OpenIdConnect {
452        open_id_connect_url: String,
453    },
454}
455
456#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
457#[serde(default, rename_all = "camelCase")]
458pub struct OAuthFlows {
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub implicit: Option<OAuthFlow>,
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub password: Option<OAuthFlow>,
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub client_credentials: Option<OAuthFlow>,
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub authorization_code: Option<OAuthFlow>,
467    #[serde(flatten)]
468    pub extensions: Object,
469}
470
471#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
472#[serde(rename_all = "camelCase")]
473pub struct OAuthFlow {
474    pub authorization_url: String,
475    pub token_url: String,
476    #[serde(default, skip_serializing_if = "Option::is_none")]
477    pub refresh_url: Option<String>,
478    pub scopes: Map<String, String>,
479    #[serde(flatten)]
480    pub extensions: Object,
481}
482
483#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
484#[serde(rename_all = "camelCase")]
485pub struct Link {
486    // TODO operationRef XOR operationId must be specified
487    #[serde(default, skip_serializing_if = "Option::is_none")]
488    pub operation_ref: Option<String>,
489    #[serde(default, skip_serializing_if = "Option::is_none")]
490    pub operation_id: Option<String>,
491    #[serde(default, skip_serializing_if = "Map::is_empty")]
492    pub parameters: Map<String, Value>,
493    #[serde(default, skip_serializing_if = "Option::is_none")]
494    pub request_body: Option<Value>,
495    #[serde(default, skip_serializing_if = "Option::is_none")]
496    pub description: Option<String>,
497    #[serde(default, skip_serializing_if = "Option::is_none")]
498    pub server: Option<Server>,
499    #[serde(flatten)]
500    pub extensions: Object,
501}
502
503#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
504#[serde(rename_all = "camelCase")]
505pub struct Callback {
506    #[serde(flatten)]
507    pub callbacks: Map<String, PathItem>,
508    #[serde(flatten)]
509    pub extensions: Object,
510}
511
512#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
513#[serde(default, rename_all = "camelCase")]
514pub struct MediaType {
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub schema: Option<SchemaObject>,
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub example: Option<Value>,
519    #[serde(skip_serializing_if = "Option::is_none")]
520    pub examples: Option<Map<String, Example>>,
521    #[serde(skip_serializing_if = "Map::is_empty")]
522    pub encoding: Map<String, Encoding>,
523    #[serde(flatten)]
524    pub extensions: Object,
525}
526
527#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
528#[serde(rename_all = "camelCase")]
529pub struct Tag {
530    pub name: String,
531    #[serde(default, skip_serializing_if = "Option::is_none")]
532    pub description: Option<String>,
533    #[serde(default, skip_serializing_if = "Option::is_none")]
534    pub external_docs: Option<ExternalDocs>,
535    #[serde(flatten)]
536    pub extensions: Object,
537}
538
539#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
540#[serde(rename_all = "camelCase")]
541pub struct ExternalDocs {
542    #[serde(default, skip_serializing_if = "Option::is_none")]
543    pub description: Option<String>,
544    pub url: String,
545    #[serde(flatten)]
546    pub extensions: Object,
547}
548
549#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
550#[serde(default, rename_all = "camelCase")]
551pub struct Encoding {
552    #[serde(skip_serializing_if = "Option::is_none")]
553    pub content_type: Option<String>,
554    #[serde(skip_serializing_if = "Map::is_empty")]
555    pub headers: Map<String, RefOr<Header>>,
556    #[serde(skip_serializing_if = "Option::is_none")]
557    pub style: Option<String>,
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub explode: Option<bool>,
560    #[serde(skip_serializing_if = "is_false")]
561    pub allow_reserved: bool,
562    #[serde(flatten)]
563    pub extensions: Object,
564}
565
566fn is_false(b: impl std::borrow::Borrow<bool>) -> bool {
567    !b.borrow()
568}