schemadoc_diff/schemas/openapi303/
converter.rs

1use indexmap::IndexMap;
2use serde_json::Value;
3
4use crate::core::{Either, ReferenceDescriptor};
5
6use crate::schema as core;
7
8use crate::schemas::openapi303::context::*;
9use crate::schemas::openapi303::schema::*;
10
11pub const VERSION: &str = "0.1.0";
12
13struct ConvertContext<'a> {
14    pub components: &'a Option<Components>,
15}
16
17impl From<OpenApi303> for core::HttpSchema {
18    fn from(spec: OpenApi303) -> Self {
19        let paths = {
20            let context = ConvertContext {
21                components: &spec.components,
22            };
23            spec.paths.map(|paths| convert_paths(paths, &context))
24        };
25
26        let components = if let Some(components) = spec.components {
27            let schemas = components.schemas.map(|schemas| {
28                schemas
29                    .into_iter()
30                    .map(|(key, schema_ref)| {
31                        (key, convert_schema_ref(schema_ref))
32                    })
33                    .collect()
34            });
35
36            let responses = components.responses.map(|responses| {
37                responses
38                    .into_iter()
39                    .map(|(key, response_ref)| {
40                        (key, convert_response_ref(response_ref))
41                    })
42                    .collect()
43            });
44
45            let parameters = components.parameters.map(|parameters| {
46                parameters
47                    .into_iter()
48                    .map(|(key, parameter_ref)| {
49                        (key, convert_parameter_ref(parameter_ref))
50                    })
51                    .collect()
52            });
53
54            let examples = components.examples.map(|examples| {
55                examples
56                    .into_iter()
57                    .map(|(key, example_ref)| {
58                        (key, convert_example_ref(example_ref))
59                    })
60                    .collect()
61            });
62
63            let request_bodies =
64                components.request_bodies.map(|request_bodies| {
65                    request_bodies
66                        .into_iter()
67                        .map(|(key, request_body_ref)| {
68                            (key, convert_request_body_ref(request_body_ref))
69                        })
70                        .collect()
71                });
72
73            let headers = components.headers.map(|headers| {
74                headers
75                    .into_iter()
76                    .map(|(key, header_ref)| {
77                        (key, convert_header_ref(header_ref))
78                    })
79                    .collect()
80            });
81
82            let links = components.links.map(|links| {
83                links
84                    .into_iter()
85                    .map(|(key, link_ref)| (key, convert_link_ref(link_ref)))
86                    .collect()
87            });
88
89            let security_schemes =
90                components.security_schemes.map(|security_schemes| {
91                    security_schemes
92                        .into_iter()
93                        .map(|(key, security_scheme_ref)| {
94                            (
95                                key,
96                                convert_security_scheme_ref(
97                                    security_scheme_ref,
98                                ),
99                            )
100                        })
101                        .collect()
102                });
103
104            Some(core::Components {
105                schemas,
106                responses,
107                parameters,
108                examples,
109                request_bodies,
110                headers,
111                security_schemes,
112                links,
113            })
114        } else {
115            None
116        };
117
118        let external_docs = spec.external_docs.map(convert_external_doc);
119
120        let tags = spec
121            .tags
122            .map(|tags| tags.into_iter().map(convert_tag).collect());
123
124        let info = spec.info.map(convert_info);
125
126        let servers = spec
127            .servers
128            .map(|servers| servers.into_iter().map(convert_server).collect());
129
130        core::HttpSchema {
131            version: spec.openapi,
132
133            schema_source: OpenApi303::id().to_owned(),
134            schema_source_version: VERSION.to_owned(),
135            schema_version: core::HttpSchema::schema_version().to_owned(),
136
137            info,
138            servers,
139            paths,
140            components,
141            tags,
142            external_docs,
143        }
144    }
145}
146
147fn convert_paths(
148    paths: IndexMap<String, MayBeRef303<Path>>,
149    context: &ConvertContext,
150) -> IndexMap<String, core::MayBeRef<core::Path>> {
151    paths
152        .into_iter()
153        .map(|(key, path_ref)| (key, convert_path_ref(path_ref, context)))
154        .collect()
155}
156
157fn convert_path_ref(
158    path_ref: MayBeRef303<Path>,
159    context: &ConvertContext,
160) -> core::MayBeRef<core::Path> {
161    match path_ref {
162        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
163            reference: value.reference,
164        }),
165        MayBeRef303::Value(value) => {
166            core::MayBeRef::Value(convert_path(value, context))
167        }
168    }
169}
170
171fn convert_path(path: Path, context: &ConvertContext) -> core::Path {
172    let parameters = &path.parameters;
173
174    let servers = path
175        .servers
176        .map(|servers| servers.into_iter().map(convert_server).collect());
177
178    core::Path {
179        get: path
180            .get
181            .map(|op| convert_operation(op, parameters, context)),
182        put: path
183            .put
184            .map(|op| convert_operation(op, parameters, context)),
185        post: path
186            .post
187            .map(|op| convert_operation(op, parameters, context)),
188        delete: path
189            .delete
190            .map(|op| convert_operation(op, parameters, context)),
191        options: path
192            .options
193            .map(|op| convert_operation(op, parameters, context)),
194        head: path
195            .head
196            .map(|op| convert_operation(op, parameters, context)),
197        patch: path
198            .patch
199            .map(|op| convert_operation(op, parameters, context)),
200        trace: path
201            .trace
202            .map(|op| convert_operation(op, parameters, context)),
203        servers,
204        summary: path.summary,
205        description: path.description,
206    }
207}
208
209fn merge_parameters(
210    context: &ConvertContext,
211    parameters_refs: Option<Vec<MayBeRef303<Parameter>>>,
212    path_parameters_refs: &Option<Vec<MayBeRef303<Parameter>>>,
213) -> Vec<MayBeRef303<Parameter>> {
214    let mut parameters: Vec<MayBeRef303<Parameter>> = Vec::new();
215
216    if let Some(parameters_refs) = parameters_refs {
217        parameters.extend(parameters_refs);
218    }
219
220    if let Some(parameters_refs) = path_parameters_refs {
221        parameters.extend(parameters_refs.clone());
222    }
223
224    let mut visited = Vec::new();
225
226    let mut result = Vec::with_capacity(parameters.len());
227
228    for may_be_parameter in parameters {
229        let key = match &may_be_parameter {
230            MayBeRef303::Ref(value) => {
231                if let Some(parameter) =
232                    deref_parameter(context.components, value.reference())
233                {
234                    (parameter.name.clone(), parameter.r#in.clone())
235                } else {
236                    // TODO: handle the case where ref not found
237                    continue;
238                }
239            }
240            MayBeRef303::Value(value) => {
241                (value.name.clone(), value.r#in.clone())
242            }
243        };
244
245        if visited.contains(&key) {
246            continue;
247        }
248
249        result.push(may_be_parameter);
250
251        visited.push(key);
252    }
253
254    result
255}
256
257fn convert_operation(
258    operation: Operation,
259    path_parameters: &Option<Vec<MayBeRef303<Parameter>>>,
260    context: &ConvertContext,
261) -> core::Operation {
262    let merged_parameters = merge_parameters(
263        context, // TODO: pass components
264        operation.parameters,
265        path_parameters,
266    );
267
268    let parameters = merged_parameters
269        .into_iter()
270        .map(convert_parameter_ref)
271        .collect();
272
273    let request_body = operation.request_body.map(convert_request_body_ref);
274
275    let responses = operation.responses.map(|responses| {
276        responses
277            .into_iter()
278            .map(|(code, response_ref)| {
279                (code, convert_response_ref(response_ref))
280            })
281            .collect()
282    });
283
284    let external_docs = operation.external_docs.map(convert_external_doc);
285
286    let servers = operation
287        .servers
288        .map(|servers| servers.into_iter().map(convert_server).collect());
289
290    core::Operation {
291        tags: operation.tags,
292        summary: operation.summary,
293        description: operation.description,
294        external_docs,
295        operation_id: operation.operation_id,
296        responses,
297        request_body,
298        servers,
299        parameters: Some(parameters),
300        security: operation.security,
301        deprecated: operation.deprecated,
302    }
303}
304
305fn convert_request_body_ref(
306    request_body_ref: MayBeRef303<RequestBody>,
307) -> core::MayBeRef<core::RequestBody> {
308    match request_body_ref {
309        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
310            reference: value.reference,
311        }),
312        MayBeRef303::Value(value) => {
313            core::MayBeRef::Value(convert_request_body(value))
314        }
315    }
316}
317
318fn convert_request_body(request_body: RequestBody) -> core::RequestBody {
319    core::RequestBody {
320        description: request_body.description,
321        required: request_body.required,
322        content: request_body.content.map(convert_media_types),
323    }
324}
325
326fn convert_media_types(
327    media_types: IndexMap<String, MediaType>,
328) -> IndexMap<String, core::MediaType> {
329    media_types
330        .into_iter()
331        .map(|(content_type, media_type)| {
332            (content_type, convert_media_type(media_type))
333        })
334        .collect()
335}
336
337fn convert_media_type(media_type: MediaType) -> core::MediaType {
338    // media_type.example
339
340    // let example = media_type.example;
341    //
342    // let examples = media_type.examples.map(
343    //     |examples| {
344    //         let examples = if let Some(example) = example {
345    //             let example_ref = MayBeRef::Value(
346    //                 Example {
347    //                     summary: None,
348    //                     description: None,
349    //                     value: Some(example),
350    //                     external_value: None,
351    //                 }
352    //             );
353    //
354    //             examples.into_iter()
355    //                 .chain(vec![("-mt-example".to_string(), example_ref)])
356    //                 .collect::<IndexMap<_, _>>()
357    //         } else {
358    //             examples
359    //         };
360    //
361    //         examples.map(convert_examples)
362    //     }
363    // );
364    core::MediaType {
365        schema: media_type.schema.map(convert_schema_ref),
366        examples: media_type.examples.map(convert_examples),
367        encoding: media_type.encoding.map(convert_encodings),
368    }
369}
370
371fn convert_encodings(
372    encodings: IndexMap<String, Encoding>,
373) -> IndexMap<String, core::Encoding> {
374    encodings
375        .into_iter()
376        .map(|(key, encoding)| (key, convert_encoding(encoding)))
377        .collect()
378}
379
380fn convert_encoding(encoding: Encoding) -> core::Encoding {
381    core::Encoding {
382        style: encoding.style,
383        explode: encoding.explode,
384        headers: encoding.headers.map(convert_headers),
385        content_type: encoding.content_type,
386        allow_reserved: encoding.allow_reserved,
387    }
388}
389
390fn convert_response_ref(
391    response_ref: MayBeRef303<Response>,
392) -> core::MayBeRef<core::Response> {
393    match response_ref {
394        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
395            reference: value.reference,
396        }),
397        MayBeRef303::Value(response) => {
398            core::MayBeRef::Value(convert_response(response))
399        }
400    }
401}
402
403fn convert_response(response: Response) -> core::Response {
404    let links = response.links.map(|links| {
405        links
406            .into_iter()
407            .map(|(key, link_ref)| (key, convert_link_ref(link_ref)))
408            .collect()
409    });
410
411    core::Response {
412        links,
413        description: response.description,
414        headers: response.headers.map(convert_headers),
415        content: response.content.map(convert_media_types),
416    }
417}
418
419fn convert_headers(
420    headers: IndexMap<String, MayBeRef303<Header>>,
421) -> IndexMap<String, core::MayBeRef<core::Header>> {
422    headers
423        .into_iter()
424        .map(|(key, header_ref)| (key, convert_header_ref(header_ref)))
425        .collect()
426}
427
428fn convert_header_ref(
429    header_ref: MayBeRef303<Header>,
430) -> core::MayBeRef<core::Header> {
431    match header_ref {
432        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
433            reference: value.reference,
434        }),
435        MayBeRef303::Value(value) => {
436            core::MayBeRef::Value(convert_header(value))
437        }
438    }
439}
440
441fn convert_header(header: Header) -> core::Header {
442    core::Header {
443        description: header.description,
444        required: header.required,
445        deprecated: header.deprecated,
446        allow_empty_value: header.allow_empty_value,
447        style: header.style,
448        explode: header.explode,
449        allow_reserved: header.allow_reserved,
450        schema: header.schema.map(convert_schema_ref),
451        examples: header.examples.map(convert_example_values),
452        content: header.content.map(convert_media_types),
453        custom_fields: header.custom_fields,
454    }
455}
456
457fn convert_parameter_ref(
458    parameter_ref: MayBeRef303<Parameter>,
459) -> core::MayBeRef<core::Parameter> {
460    match parameter_ref {
461        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
462            reference: value.reference,
463        }),
464        MayBeRef303::Value(parameter) => {
465            core::MayBeRef::Value(convert_parameter(parameter))
466        }
467    }
468}
469
470fn convert_parameter(parameter: Parameter) -> core::Parameter {
471    let schema = parameter.schema.map(convert_schema_ref);
472    core::Parameter {
473        name: parameter.name,
474        r#in: parameter.r#in,
475        description: parameter.description,
476        required: parameter.required,
477        deprecated: parameter.deprecated,
478        allow_empty_value: parameter.allow_empty_value,
479        style: parameter.style,
480        explode: parameter.explode,
481        allow_reserved: parameter.allow_reserved,
482        schema,
483        examples: parameter.examples.map(convert_example_values),
484        content: parameter.content.map(convert_media_types),
485        custom_fields: parameter.custom_fields,
486    }
487}
488
489fn convert_schema_ref(
490    schema_ref: MayBeRef303<Schema>,
491) -> core::MayBeRef<core::Schema> {
492    match schema_ref {
493        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
494            reference: value.reference,
495        }),
496        MayBeRef303::Value(value) => {
497            core::MayBeRef::Value(convert_schema(value))
498        }
499    }
500}
501
502fn convert_schema(schema: Schema) -> core::Schema {
503    let r#type = if let Some(nullable) = schema.nullable {
504        if let Some(type_) = schema.r#type {
505            match type_ {
506                Either::Left(single) => {
507                    if nullable {
508                        Some(Either::Right(Box::new(vec![
509                            single,
510                            "null".to_string(),
511                        ])))
512                    } else {
513                        Some(Either::Left(single))
514                    }
515                }
516                Either::Right(multiple) => {
517                    let mut values = multiple;
518
519                    if nullable {
520                        values.push("null".to_string());
521                    }
522
523                    Some(Either::Right(values))
524                }
525            }
526        } else {
527            Some(Either::Right(Box::new(vec![
528                "object".to_string(),
529                "null".to_string(),
530            ])))
531        }
532    } else {
533        schema.r#type
534    };
535
536    let all_of = schema
537        .all_of
538        .map(|values| values.into_iter().map(convert_schema_ref).collect());
539
540    let one_of = schema
541        .one_of
542        .map(|values| values.into_iter().map(convert_schema_ref).collect());
543
544    let any_of = schema
545        .any_of
546        .map(|values| values.into_iter().map(convert_schema_ref).collect());
547
548    let not = schema
549        .not
550        .map(|values| values.into_iter().map(convert_schema_ref).collect());
551
552    let items = schema.items.map(convert_schema_ref);
553
554    let properties = schema.properties.map(|properties| {
555        properties
556            .into_iter()
557            .map(|(key, property_ref)| (key, convert_schema_ref(property_ref)))
558            .collect()
559    });
560
561    let additional_properties =
562        schema.additional_properties.map(|additional_properties| {
563            match additional_properties {
564                Either::Left(value) => Either::Left(value),
565                Either::Right(schema_ref) => {
566                    Either::Right(Box::new(convert_schema_ref(*schema_ref)))
567                }
568            }
569        });
570
571    let discriminator =
572        schema
573            .discriminator
574            .map(|discriminator| core::Discriminator {
575                property_name: discriminator.property_name,
576                mapping: discriminator.mapping,
577            });
578
579    let xml = schema.xml.map(convert_xml);
580
581    let external_docs = schema.external_docs.map(convert_external_doc);
582
583    core::Schema {
584        title: schema.title,
585        multiple_of: schema.multiple_of,
586        maximum: schema.maximum,
587        exclusive_maximum: schema.exclusive_maximum,
588        minimum: schema.minimum,
589        exclusive_minimum: schema.exclusive_minimum,
590        max_length: schema.max_length,
591        min_length: schema.min_length,
592        pattern: schema.pattern,
593        max_items: schema.max_items,
594        min_items: schema.min_items,
595        unique_items: schema.unique_items,
596        max_properties: schema.max_properties,
597        min_properties: schema.min_properties,
598        required: schema.required,
599        r#enum: schema.r#enum,
600        r#type,
601        all_of,
602        one_of,
603        any_of,
604        not,
605        items: Box::new(items),
606        properties,
607        additional_properties,
608        description: schema.description,
609        format: schema.format,
610        default: schema.default,
611        discriminator,
612        read_only: schema.read_only,
613        write_only: schema.write_only,
614        xml,
615        external_docs,
616        example: schema.example,
617        deprecated: schema.deprecated,
618        custom_fields: schema.custom_fields,
619    }
620}
621
622fn convert_external_doc(external_doc: ExternalDoc) -> core::ExternalDoc {
623    core::ExternalDoc {
624        url: external_doc.url,
625        description: external_doc.description,
626    }
627}
628
629fn convert_xml(external_docs: Xml) -> core::Xml {
630    core::Xml {
631        name: external_docs.name,
632        namespace: external_docs.namespace,
633        prefix: external_docs.prefix,
634        attribute: external_docs.attribute,
635        wrapped: external_docs.wrapped,
636    }
637}
638
639fn convert_link_ref(
640    link_ref: MayBeRef303<Link>,
641) -> core::MayBeRef<core::Link> {
642    match link_ref {
643        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
644            reference: value.reference,
645        }),
646        MayBeRef303::Value(value) => {
647            core::MayBeRef::Value(convert_link(value))
648        }
649    }
650}
651
652fn convert_link(link: Link) -> core::Link {
653    core::Link {
654        operation_ref: link.operation_ref,
655        operation_id: link.operation_id,
656        parameters: link.parameters,
657        request_body: link.request_body,
658        description: link.description,
659        server: None,
660    }
661}
662
663fn convert_examples(
664    examples: IndexMap<String, MayBeRef303<Example>>,
665) -> IndexMap<String, core::MayBeRef<core::Example>> {
666    examples
667        .into_iter()
668        .map(|(key, example_ref)| (key, convert_example_ref(example_ref)))
669        .collect()
670}
671
672fn convert_example_values(
673    examples: IndexMap<String, MayBeRef303<Value>>,
674) -> IndexMap<String, core::MayBeRef<Value>> {
675    examples
676        .into_iter()
677        .map(|(key, example)| {
678            let example_ref = match example {
679                MayBeRef303::Ref(value) => {
680                    core::MayBeRef::Ref(core::HttpSchemaRef {
681                        reference: value.reference,
682                    })
683                }
684                MayBeRef303::Value(value) => core::MayBeRef::Value(value),
685            };
686            (key, example_ref)
687        })
688        .collect()
689}
690
691fn convert_example_ref(
692    example_ref: MayBeRef303<Example>,
693) -> core::MayBeRef<core::Example> {
694    match example_ref {
695        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
696            reference: value.reference,
697        }),
698        MayBeRef303::Value(value) => {
699            core::MayBeRef::Value(convert_example(value))
700        }
701    }
702}
703
704fn convert_example(example: Example) -> core::Example {
705    core::Example {
706        summary: example.summary,
707        description: example.description,
708        value: example.value,
709        external_value: example.external_value,
710    }
711}
712
713fn convert_security_scheme_ref(
714    security_scheme_ref: MayBeRef303<SecurityScheme>,
715) -> core::MayBeRef<core::SecurityScheme> {
716    match security_scheme_ref {
717        MayBeRef303::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
718            reference: value.reference,
719        }),
720        MayBeRef303::Value(value) => {
721            core::MayBeRef::Value(convert_security_scheme(value))
722        }
723    }
724}
725
726fn convert_security_scheme(
727    security_scheme: SecurityScheme,
728) -> core::SecurityScheme {
729    core::SecurityScheme {
730        r#type: security_scheme.r#type,
731        description: security_scheme.description,
732        name: security_scheme.name,
733        r#in: security_scheme.r#in,
734        scheme: security_scheme.scheme,
735        bearer_format: security_scheme.bearer_format,
736        flows: security_scheme.flows.map(convert_oauth_flows),
737        open_id_connect_url: security_scheme.open_id_connect_url,
738    }
739}
740
741fn convert_oauth_flows(oauth_flows: OAuthFlows) -> core::OAuthFlows {
742    core::OAuthFlows {
743        implicit: oauth_flows.implicit.map(convert_oauth_flow),
744        password: oauth_flows.password.map(convert_oauth_flow),
745        client_credentials: oauth_flows
746            .client_credentials
747            .map(convert_oauth_flow),
748        authorization_code: oauth_flows
749            .authorization_code
750            .map(convert_oauth_flow),
751    }
752}
753
754fn convert_oauth_flow(oauth_flow: OAuthFlow) -> core::OAuthFlow {
755    core::OAuthFlow {
756        authorization_url: oauth_flow.authorization_url,
757        token_url: oauth_flow.token_url,
758        refresh_url: oauth_flow.refresh_url,
759        scopes: oauth_flow.scopes,
760    }
761}
762
763fn convert_tag(tag: Tag) -> core::Tag {
764    core::Tag {
765        name: tag.name,
766        description: tag.description,
767        external_doc: tag.external_doc.map(convert_external_doc),
768    }
769}
770
771fn convert_info(info: Info) -> core::Info {
772    core::Info {
773        title: info.title,
774        description: info.description,
775        terms_of_service: info.terms_of_service,
776        contact: info.contact.map(convert_contact),
777        license: info.license.map(convert_license),
778        version: info.version,
779    }
780}
781
782fn convert_contact(contact: Contact) -> core::Contact {
783    core::Contact {
784        name: contact.name,
785        url: contact.url,
786        email: contact.email,
787    }
788}
789
790fn convert_license(license: License) -> core::License {
791    core::License {
792        name: license.name,
793        url: license.url,
794    }
795}
796
797fn convert_server(server: Server) -> core::Server {
798    core::Server {
799        url: server.url,
800        description: server.description,
801        variables: server.variables.map(|variables| {
802            variables
803                .into_iter()
804                .map(|(key, variable)| {
805                    (key, convert_server_variable(variable))
806                })
807                .collect()
808        }),
809    }
810}
811
812fn convert_server_variable(
813    server_variable: ServerVariable,
814) -> core::ServerVariable {
815    core::ServerVariable {
816        r#enum: server_variable.r#enum,
817        default: server_variable.default,
818        description: server_variable.description,
819    }
820}