schemadoc_diff/schemas/openapi310/
schema.rs

1use serde::{Deserialize, Serialize};
2use std::fmt::Debug;
3
4use indexmap::IndexMap;
5use serde_json::Value;
6
7use crate::core::{DiffResult, Either, MayBeRefCore, ReferenceDescriptor};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct OpenApi310Ref {
11    #[serde(rename = "$ref")]
12    pub reference: String,
13}
14
15impl ReferenceDescriptor for OpenApi310Ref {
16    fn reference(&self) -> &str {
17        &self.reference
18    }
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct OpenApi310RefDiff {
23    #[serde(rename = "$ref")]
24    pub reference: DiffResult<String>,
25}
26
27impl ReferenceDescriptor for OpenApi310RefDiff {
28    fn reference(&self) -> &str {
29        self.reference.get().expect("Reference diff cannot be null")
30    }
31}
32
33pub type MayBeRef310<T> = MayBeRefCore<T, OpenApi310Ref>;
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct OpenApi310 {
38    pub openapi: String,
39    pub info: Option<Info>,
40    pub servers: Option<Vec<Server>>,
41    pub paths: Option<IndexMap<String, MayBeRef310<Path>>>,
42    pub components: Option<Components>,
43    // TODO:
44    // pub security:
45    pub tags: Option<Vec<Tag>>,
46    pub external_docs: Option<ExternalDoc>,
47}
48
49impl OpenApi310 {
50    pub const fn id() -> &'static str {
51        "OpenApi310"
52    }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct Info {
58    pub title: Option<String>,
59    pub description: Option<String>,
60    pub terms_of_service: Option<String>,
61
62    pub contact: Option<Contact>,
63    pub license: Option<License>,
64
65    pub version: Option<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct Contact {
70    pub name: Option<String>,
71    pub url: Option<String>,
72    pub email: Option<String>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct License {
77    pub name: Option<String>,
78    pub url: Option<String>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Server {
83    pub url: Option<String>,
84    pub description: Option<String>,
85    pub variables: Option<IndexMap<String, ServerVariable>>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ServerVariable {
90    pub r#enum: Option<Vec<String>>,
91    pub default: Option<Value>,
92    pub description: Option<String>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct Components {
98    pub schemas: Option<IndexMap<String, MayBeRef310<Schema>>>,
99    pub responses: Option<IndexMap<String, MayBeRef310<Response>>>,
100    pub parameters: Option<IndexMap<String, MayBeRef310<Parameter>>>,
101    pub examples: Option<IndexMap<String, MayBeRef310<Example>>>,
102    pub request_bodies: Option<IndexMap<String, MayBeRef310<RequestBody>>>,
103    pub headers: Option<IndexMap<String, MayBeRef310<Header>>>,
104    pub security_schemes:
105        Option<IndexMap<String, MayBeRef310<SecurityScheme>>>,
106    pub links: Option<IndexMap<String, MayBeRef310<Link>>>,
107    // pub callbacks: Option<HashMap<String, MayBeRef310<Header>>>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ExternalDoc {
112    pub url: Option<String>,
113    pub description: Option<String>,
114}
115
116#[derive(Serialize, Deserialize, Debug, Clone, Default)]
117#[serde(rename_all = "camelCase")]
118pub struct Parameter {
119    pub name: String,
120    pub r#in: String,
121
122    pub description: Option<String>,
123
124    pub required: Option<bool>,
125    pub deprecated: Option<bool>,
126    pub allow_empty_value: Option<bool>,
127
128    pub style: Option<String>,
129    pub explode: Option<bool>,
130    pub allow_reserved: Option<bool>,
131
132    pub schema: Option<MayBeRef310<Schema>>,
133
134    pub example: Option<Value>,
135    pub examples: Option<IndexMap<String, MayBeRef310<Value>>>,
136
137    pub content: Option<IndexMap<String, MediaType>>,
138
139    #[serde(flatten)]
140    pub custom_fields: IndexMap<String, Value>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct RequestBody {
145    pub description: Option<String>,
146    pub content: Option<IndexMap<String, MediaType>>,
147    pub required: Option<bool>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct MediaType {
152    pub schema: Option<MayBeRef310<Schema>>,
153    pub example: Option<Value>,
154
155    pub examples: Option<IndexMap<String, MayBeRef310<Example>>>,
156    pub encoding: Option<IndexMap<String, Encoding>>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct Encoding {
162    pub content_type: Option<String>,
163    pub headers: Option<IndexMap<String, MayBeRef310<Header>>>,
164    pub style: Option<String>,
165    pub explode: Option<bool>,
166    pub allow_reserved: Option<bool>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct Link {
172    pub operation_ref: Option<String>,
173    pub operation_id: Option<String>,
174    pub parameters: Option<IndexMap<String, Value>>,
175    pub request_body: Option<Value>,
176    pub description: Option<String>,
177    pub server: Option<Server>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct Response {
182    pub description: Option<String>,
183    pub headers: Option<IndexMap<String, MayBeRef310<Header>>>,
184    pub content: Option<IndexMap<String, MediaType>>,
185
186    pub links: Option<IndexMap<String, MayBeRef310<Link>>>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct Example {
192    pub summary: Option<String>,
193    pub description: Option<String>,
194    pub value: Option<Value>,
195    pub external_value: Option<String>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct Discriminator {
201    pub property_name: Option<String>,
202    pub mapping: Option<IndexMap<String, String>>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct Xml {
208    pub name: Option<String>,
209    pub namespace: Option<String>,
210    pub prefix: Option<String>,
211    pub attribute: Option<bool>,
212    pub wrapped: Option<bool>,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217pub struct SecurityScheme {
218    pub r#type: Option<String>,
219    pub description: Option<String>,
220    pub name: Option<String>,
221    pub r#in: Option<String>,
222    pub scheme: Option<String>,
223    pub bearer_format: Option<String>,
224    pub flows: Option<OAuthFlows>,
225    pub open_id_connect_url: Option<String>,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct OAuthFlows {
231    pub implicit: Option<OAuthFlow>,
232    pub password: Option<OAuthFlow>,
233    pub client_credentials: Option<OAuthFlow>,
234    pub authorization_code: Option<OAuthFlow>,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct OAuthFlow {
240    pub authorization_url: Option<String>,
241    pub token_url: Option<String>,
242    pub refresh_url: Option<String>,
243    pub scopes: Option<IndexMap<String, String>>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct Tag {
249    pub name: Option<String>,
250    pub description: Option<String>,
251    pub external_doc: Option<ExternalDoc>,
252}
253
254#[derive(Debug, Clone, Default, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct Schema {
257    // https://json-schema.org/draft/2020-12/json-schema-core.html
258
259    // #[serde(rename = "$schema")]
260    // schema_: Option<String>,
261
262    // #[serde(rename = "id")]
263    // id_: Option<String>,
264    // #[serde(rename = "$anchor")]
265    // anchor_: Option<String>,
266    //
267    // #[serde(rename = "$dynamicAnchor")]
268    // dynamic_anchor_: Option<String>,
269    //
270    // #[serde(rename = "$dynamicRef")]
271    // dynamic_ref_: Option<String>,
272    //
273    // #[serde(rename = "$def")]
274    // defs_: Option<HashMap<String, Schema>>,
275    //
276    // #[serde(rename = "$comment")]
277    // comment_: Option<HashMap<String, Schema>>,
278
279    // if
280    // then
281    // else
282    // dependentSchemas
283
284    // contains
285    pub title: Option<String>,
286    pub multiple_of: Option<f32>,
287    pub maximum: Option<f32>,
288    pub exclusive_maximum: Option<bool>,
289    pub minimum: Option<f32>,
290    pub exclusive_minimum: Option<bool>,
291    pub max_length: Option<usize>,
292    pub min_length: Option<usize>,
293    pub pattern: Option<String>,
294    pub max_items: Option<usize>,
295    pub min_items: Option<usize>,
296    pub unique_items: Option<bool>,
297    pub max_properties: Option<usize>,
298    pub min_properties: Option<usize>,
299    pub required: Option<Vec<String>>,
300    pub r#enum: Option<Vec<Value>>,
301
302    // r#const: Option<Vec>,
303
304    // pub contains: Box<Option<MayBeRef310<Schema>>>
305    // pub min_contains: Option<usize>,
306    // pub max_contains: Option<bool>,
307
308    // dependentRequired
309
310    // https://json-schema.org/draft/2020-12/json-schema-validation.html#:~:text=to%20these%20keywords.-,8.3.,-contentEncoding
311    // content_encoding
312    // contentMediaType
313    // contentSchema
314    pub r#type: Option<Either<String, Vec<String>>>,
315    pub all_of: Option<Vec<MayBeRef310<Schema>>>,
316    pub one_of: Option<Vec<MayBeRef310<Schema>>>,
317    pub any_of: Option<Vec<MayBeRef310<Schema>>>,
318    pub not: Option<Vec<MayBeRef310<Schema>>>,
319
320    pub items: Box<Option<MayBeRef310<Schema>>>,
321
322    // prefix_items: Option<Vec<MayBeRef310<Schema>>>,
323
324    // unevaluated_items: Box<Option<MayBeRef310<Schema>>>
325    pub properties: Option<IndexMap<String, MayBeRef310<Schema>>>,
326    pub additional_properties: Option<Either<bool, MayBeRef310<Schema>>>, // TODO: Can be Bool in 3.1??
327    // pattern_properties: Option<HashMap<String, MayBeRef310<Schema>>>,
328
329    // property_names: Box<Option<MayBeRef310<Schema>>>,
330
331    // unevaluated_properties: Box<Option<MayBeRef310<Schema>>>
332    pub description: Option<String>,
333    pub format: Option<String>,
334    pub default: Option<Value>,
335
336    pub discriminator: Option<Discriminator>,
337    pub read_only: Option<bool>,
338    pub write_only: Option<bool>,
339    pub xml: Option<Xml>,
340    pub external_docs: Option<ExternalDoc>,
341    pub example: Option<Value>,
342
343    // examples: Option<Vec<Value>>,
344    pub deprecated: Option<bool>,
345
346    #[serde(flatten)]
347    pub custom_fields: IndexMap<String, Value>,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
351#[serde(rename_all = "camelCase")]
352pub struct Header {
353    pub description: Option<String>,
354
355    pub required: Option<bool>,
356    pub deprecated: Option<bool>,
357    pub allow_empty_value: Option<bool>,
358
359    pub style: Option<String>,
360    pub explode: Option<bool>,
361    pub allow_reserved: Option<bool>,
362
363    pub schema: Option<MayBeRef310<Schema>>,
364
365    pub example: Option<Value>,
366    pub examples: Option<IndexMap<String, MayBeRef310<Value>>>,
367
368    pub content: Option<IndexMap<String, MediaType>>,
369
370    #[serde(flatten)]
371    pub custom_fields: IndexMap<String, Value>,
372}
373
374#[derive(Debug, Clone, Default, Serialize, Deserialize)]
375#[serde(rename_all = "camelCase")]
376pub struct Operation {
377    pub tags: Option<Vec<String>>,
378    pub summary: Option<String>,
379    pub description: Option<String>,
380
381    pub external_docs: Option<ExternalDoc>,
382
383    pub operation_id: Option<String>,
384
385    pub parameters: Option<Vec<MayBeRef310<Parameter>>>,
386
387    pub responses: Option<IndexMap<String, MayBeRef310<Response>>>,
388
389    pub request_body: Option<MayBeRef310<RequestBody>>,
390    pub servers: Option<Vec<Server>>,
391
392    pub security: Option<Vec<IndexMap<String, Vec<String>>>>,
393
394    // TODO:
395    // pub callbacks
396    pub deprecated: Option<bool>,
397}
398
399#[derive(Debug, Clone, Default, Serialize, Deserialize)]
400pub struct Path {
401    pub get: Option<Operation>,
402    pub put: Option<Operation>,
403    pub post: Option<Operation>,
404    pub delete: Option<Operation>,
405    pub options: Option<Operation>,
406    pub head: Option<Operation>,
407    pub patch: Option<Operation>,
408    pub trace: Option<Operation>,
409
410    pub servers: Option<Vec<Server>>,
411    pub parameters: Option<Vec<MayBeRef310<Parameter>>>,
412
413    pub summary: Option<String>,
414    pub description: Option<String>,
415}
416
417#[cfg(test)]
418mod tests {
419    use crate::schemas::openapi310::schema::*;
420    use indexmap::IndexMap;
421    use serde_json::json;
422
423    #[test]
424    fn check_request() {
425        let content = json!(
426            {"AuthentiqID":{"content":{"application/jwt":{"schema":{"$ref":"#/components/schemas/AuthentiqID"}}},"description":"Authentiq ID to register","required":true}}
427        );
428
429        let _: Option<IndexMap<String, MayBeRef310<RequestBody>>> =
430            serde_json::from_value(content).unwrap();
431    }
432
433    #[test]
434    fn check_operation() {
435        let op_def = r#"{
436      "post": {
437        "tags": ["Nodes"],
438        "summary": "Export Xlsx Template",
439        "description": "Generate XLSX-template for aggregated node data editing",
440        "operationId": "export_xlsx_template_api_v2_nodes__path__template_generate__post",
441        "parameters": [
442          {
443            "required": true,
444            "schema": { "title": "Path", "type": "string" },
445            "name": "path",
446            "in": "path"
447          },
448          {
449            "required": false,
450            "schema": { "title": "Update Sender", "type": "string" },
451            "name": "update_sender",
452            "in": "query"
453          },
454          {
455            "required": false,
456            "schema": { "title": "Force", "type": "boolean", "default": false },
457            "name": "force",
458            "in": "query"
459          },
460          {
461            "required": false,
462            "schema": { "title": "Compound Amount", "type": "integer" },
463            "name": "compound_amount",
464            "in": "query"
465          },
466          {
467            "required": false,
468            "schema": {
469              "allOf": [{ "$ref": "/components/schemas/ExportFmt" }],
470              "default": "xlsx"
471            },
472            "name": "export_format",
473            "in": "query"
474          }
475        ],
476        "requestBody": {
477          "content": {
478            "application/json": {
479              "schema": {
480                "$ref": "/components/schemas/Body_export_xlsx_template_api_v2_nodes__path__template_generate__post"
481              }
482            }
483          }
484        },
485        "responses": {
486          "200": {
487            "description": "Successful Response",
488            "content": {
489              "application/json": { "schema": {} },
490              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {}
491            }
492          },
493          "422": {
494            "description": "Validation Error",
495            "content": {
496              "application/json": {
497                "schema": { "$ref": "/components/schemas/HTTPValidationError" }
498              }
499            }
500          }
501        },
502        "security": [{ "OAuth2PasswordBearer": [] }]
503      }
504    }"#;
505
506        let _: Path = serde_json::from_str(op_def).unwrap();
507    }
508
509    #[test]
510    fn check_schem_additional_properties() {
511        let op_def = r#"{
512            "title": "AdditionalProperties",
513            "type": "object",
514            "additionalProperties": {
515              "$ref": "/components/schemas/AdditionalProperties"
516            }
517          }"#;
518
519        let op: Schema = serde_json::from_str(op_def).unwrap();
520        assert!(matches!(op.additional_properties, Some(Either::Right(_))));
521
522        let op_def = r#"{
523            "title": "AdditionalProperties",
524            "type": "object",
525            "additionalProperties": false
526          }"#;
527
528        let op: Schema = serde_json::from_str(op_def).unwrap();
529        assert!(matches!(op.additional_properties, Some(Either::Left(_))));
530
531        let sc_def = r#"
532        {
533        "type": "object",
534        "discriminator": { "propertyName": "type" },
535        "properties": {
536          "type": {
537            "type": "string",
538            "description": "The type of context being attached to the entity.",
539            "enum": ["link", "image"]
540          }
541        },
542        "required": ["type"]
543      }
544        "#;
545        let op: Schema = serde_json::from_str(sc_def).unwrap();
546        assert!(matches!(op.discriminator, Some(_)))
547    }
548}