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