1use crate::core::{Either, ReferenceDescriptor};
2use indexmap::IndexMap;
3
4use crate::schema as core;
5
6use crate::schemas::swagger2::context::*;
7use crate::schemas::swagger2::schema::*;
8
9pub const VERSION: &str = "0.1.0";
10
11struct ConvertContext<'a> {
12 pub consumes: &'a Option<Vec<String>>,
13 pub produces: &'a Option<Vec<String>>,
14 pub parameters: &'a Option<IndexMap<String, Parameter>>,
16 pub responses: &'a Option<IndexMap<String, Response>>,
17}
18
19impl From<SwaggerV2> for core::HttpSchema {
20 fn from(spec: SwaggerV2) -> Self {
21 let paths = {
22 let context = ConvertContext {
23 consumes: &spec.consumes,
24 produces: &spec.produces,
25 parameters: &spec.parameters,
27 responses: &spec.responses,
28 };
29
30 spec.paths.map(|paths| convert_paths(paths, &context))
31 };
32
33 let parameters = spec.parameters.map(|parameters| {
34 parameters
35 .into_iter()
36 .map(|(key, parameter)| {
37 (key, core::MayBeRef::Value(convert_parameter(parameter)))
38 })
39 .collect::<IndexMap<_, _>>()
40 });
41
42 let responses = spec.responses.map(|responses| {
43 responses
44 .into_iter()
45 .map(|(key, response)| {
46 (
47 key,
48 core::MayBeRef::Value(convert_response(
49 response,
50 &["application/json".to_string()],
51 )),
52 )
53 })
54 .collect::<IndexMap<_, _>>()
55 });
56
57 let schemas = spec.definitions.map(|definitions| {
58 definitions
59 .into_iter()
60 .map(|(key, schema)| {
61 (key, core::MayBeRef::Value(convert_schema(schema)))
62 })
63 .collect::<IndexMap<_, _>>()
64 });
65
66 let components = core::Components {
67 schemas,
68 responses,
69 parameters,
70 examples: None,
71 request_bodies: None,
72 headers: None,
73 security_schemes: None,
74 links: None,
75 };
76
77 let info = spec.info.map(convert_info);
78
79 core::HttpSchema {
80 version: spec.swagger,
81
82 schema_source: SwaggerV2::id().to_owned(),
83 schema_source_version: VERSION.to_owned(),
84 schema_version: core::HttpSchema::schema_version().to_owned(),
85
86 info,
87 servers: None,
88 paths,
89 components: Some(components),
90 tags: None,
91 external_docs: None,
92 }
93 }
94}
95
96fn convert_paths(
97 paths: IndexMap<String, Path>,
98 context: &ConvertContext,
99) -> IndexMap<String, core::MayBeRef<core::Path>> {
100 paths
101 .into_iter()
102 .map(|(key, path)| {
103 (key, core::MayBeRef::Value(convert_path(path, context)))
104 })
105 .collect()
106}
107
108fn convert_path(path: Path, context: &ConvertContext) -> core::Path {
109 let parameters = &path.parameters;
110
111 core::Path {
112 get: path
113 .get
114 .map(|op| convert_operation(op, parameters, context)),
115 put: path
116 .put
117 .map(|op| convert_operation(op, parameters, context)),
118 post: path
119 .post
120 .map(|op| convert_operation(op, parameters, context)),
121 delete: path
122 .delete
123 .map(|op| convert_operation(op, parameters, context)),
124 options: path
125 .options
126 .map(|op| convert_operation(op, parameters, context)),
127 head: path
128 .head
129 .map(|op| convert_operation(op, parameters, context)),
130 patch: path
131 .patch
132 .map(|op| convert_operation(op, parameters, context)),
133 trace: None,
134 servers: None,
135 summary: None,
136 description: None,
137 }
138}
139
140fn convert_info(info: Info) -> core::Info {
141 core::Info {
142 title: info.title,
143 version: info.version,
144 description: info.description,
145 terms_of_service: info.terms_of_service,
146 contact: info.contact.map(convert_contact),
147 license: info.license.map(convert_license),
148 }
149}
150
151fn convert_contact(contact: Contact) -> core::Contact {
152 core::Contact {
153 url: contact.url,
154 name: contact.name,
155 email: contact.email,
156 }
157}
158
159fn convert_license(license: License) -> core::License {
160 core::License {
161 url: license.url,
162 name: license.name,
163 }
164}
165
166fn merge_parameters(
167 components: &Option<IndexMap<String, Parameter>>,
168 parameters_refs: Option<Vec<MayBeRef200<Parameter>>>,
169 path_parameters_refs: &Option<Vec<MayBeRef200<Parameter>>>,
170) -> Vec<(String, String, MayBeRef200<Parameter>)> {
171 let mut parameters: Vec<MayBeRef200<Parameter>> = Vec::new();
172
173 if let Some(parameters_refs) = parameters_refs {
174 parameters.extend(parameters_refs);
175 }
176
177 if let Some(parameters_refs) = path_parameters_refs {
178 parameters.extend(parameters_refs.clone());
179 }
180
181 let mut visited = Vec::new();
182
183 let mut result = Vec::with_capacity(parameters.len());
184
185 for may_be_parameter in parameters {
186 let key = match &may_be_parameter {
187 MayBeRef200::Ref(value) => {
188 if let Some(parameter) =
189 deref_parameter(components, value.reference())
190 {
191 (parameter.name.clone(), parameter.r#in.clone())
192 } else {
193 continue;
195 }
196 }
197 MayBeRef200::Value(value) => {
198 (value.name.clone(), value.r#in.clone())
199 }
200 };
201
202 if visited.contains(&key) {
203 continue;
204 }
205
206 result.push((key.0.clone(), key.1.clone(), may_be_parameter));
207
208 visited.push(key);
209 }
210
211 result
212}
213
214fn convert_operation(
215 operation: Operation,
216 path_parameters: &Option<Vec<MayBeRef200<Parameter>>>,
217 context: &ConvertContext,
218) -> core::Operation {
219 let merged_parameters = merge_parameters(
220 context.parameters, operation.parameters,
222 path_parameters,
223 );
224
225 if operation.operation_id == Some("workspace-regions_create".to_string()) {
226 println!("found");
227 }
228
229 let mut body_parameters: Vec<_> = Vec::with_capacity(1);
230 let mut parameters: Vec<_> = Vec::with_capacity(merged_parameters.len());
231
232 for (_name, loc, parameter) in merged_parameters {
233 if loc == "body" || loc == "formData" {
234 body_parameters.push(parameter)
235 } else {
236 parameters.push(convert_parameter_ref(parameter))
237 }
238 }
239
240 let request_body =
241 convert_to_request_body(body_parameters, &operation.consumes, context);
242
243 let mut produces = operation
244 .produces
245 .unwrap_or_else(|| context.produces.clone().unwrap_or_default());
246
247 if produces.is_empty() {
248 produces.push("application/json".to_string())
249 }
250
251 let unref = context
252 .produces
253 .as_ref()
254 .map(|global_produces| global_produces != &produces)
255 .unwrap_or_else(|| produces != vec!["application/json".to_string()]);
256
257 let responses = operation
258 .responses
259 .into_iter()
260 .map(|(code, response_ref)| {
261 (
262 code,
263 convert_response_ref(response_ref, &produces, context, unref),
264 )
265 })
266 .collect();
267
268 let external_docs = operation.external_docs.map(convert_external_docs);
269
270 core::Operation {
271 tags: Some(operation.tags),
272 summary: operation.summary,
273 description: operation.description,
274 external_docs,
275 operation_id: operation.operation_id,
276 responses: Some(responses),
277 request_body,
278 servers: None,
279 parameters: Some(parameters),
280 security: None, deprecated: operation.deprecated,
282 }
283}
284
285fn convert_to_request_body(
286 parameters: Vec<MayBeRef200<Parameter>>,
287 consumes: &Option<Vec<String>>,
288 context: &ConvertContext,
289) -> Option<core::MayBeRef<core::RequestBody>> {
290 if let Some(body) = parameters.first() {
291 let parameter = match body {
292 MayBeRef200::Ref(value) => {
293 deref_parameter(context.parameters, value.reference())
294 }
295 MayBeRef200::Value(value) => Some(value),
296 };
297
298 if let Some(parameter) = parameter {
300 let schema = if let Some(schema) = parameter.schema.clone() {
301 schema
302 } else {
303 MayBeRef200::Value(Schema {
304 multiple_of: parameter.multiple_of,
305 maximum: parameter.maximum,
306 exclusive_maximum: parameter.exclusive_maximum,
307 minimum: parameter.minimum,
308 exclusive_minimum: parameter.exclusive_minimum,
309 max_length: parameter.max_length,
310 min_length: parameter.min_length,
311 pattern: parameter.pattern.clone(),
312 max_items: parameter.max_items,
313 min_items: parameter.min_items,
314 unique_items: parameter.unique_items,
315 r#enum: parameter.r#enum.clone(),
316 r#type: parameter.r#type.clone(),
317 items: Box::new(parameter.items.clone()),
318 format: parameter.format.clone(),
319 default: parameter.default.clone(),
320 ..Default::default()
321 })
322 };
323
324 let mut media_types = consumes.clone().unwrap_or_else(|| {
325 context.consumes.clone().unwrap_or_default()
326 });
327
328 if media_types.is_empty() {
329 media_types.push("application/json".to_string())
330 }
331
332 let content = convert_schema_to_media_type(schema, media_types);
333
334 Some(core::MayBeRef::Value(core::RequestBody {
335 content: Some(content),
336 required: parameter.required,
337 description: parameter.description.clone(),
338 }))
339 } else {
340 None
341 }
342 } else {
343 None
344 }
345}
346
347fn convert_response_ref(
348 response_ref: MayBeRef200<Response>,
349 produces: &[String],
350 context: &ConvertContext,
351 unref: bool,
352) -> core::MayBeRef<core::Response> {
353 match response_ref {
354 MayBeRef200::Ref(value) => {
355 if unref {
356 let reference = value.reference.replace("#/responses/", "");
357
358 let response = context
359 .responses
360 .as_ref()
361 .and_then(|responses| responses.get(&reference).cloned())
362 .unwrap();
363
364 core::MayBeRef::Value(convert_response(response, produces))
365 } else {
366 core::MayBeRef::Ref(core::HttpSchemaRef {
367 reference: value
368 .reference
369 .replace("#/responses", "#/components/responses"),
370 })
371 }
372 }
373 MayBeRef200::Value(response) => {
374 core::MayBeRef::Value(convert_response(response, produces))
375 }
376 }
377}
378
379fn convert_response(
380 response: Response,
381 produces: &[String],
382) -> core::Response {
383 let headers = response.headers.map(|headers| {
384 headers
385 .into_iter()
386 .map(|(key, header)| {
387 (key, core::MayBeRef::Value(convert_header(header)))
388 })
389 .collect()
390 });
391
392 let content = response
393 .schema
394 .map(|sc| convert_schema_to_media_type(sc, produces.to_owned()));
395
396 core::Response {
397 description: response.description,
398 headers,
399 content,
400 links: None,
401 }
402}
403
404fn convert_schema_to_media_type(
405 schema: MayBeRef200<Schema>,
406 media_types: Vec<String>,
407) -> IndexMap<String, core::MediaType> {
408 let media_type = core::MediaType {
409 schema: Some(convert_schema_ref(schema)),
410 examples: None,
411 encoding: None,
412 };
413
414 media_types
415 .into_iter()
416 .map(|mime_type| (mime_type, media_type.clone()))
417 .collect()
418}
419
420fn convert_header(header: Header) -> core::Header {
421 let items = header.items.map(convert_schema_ref);
422
423 let explode = header.format.as_ref().map(|format| format == "multi");
424
425 let schema = core::Schema {
426 multiple_of: header.multiple_of,
427 maximum: header.maximum,
428 exclusive_maximum: header.exclusive_maximum,
429 minimum: header.minimum,
430 exclusive_minimum: header.exclusive_minimum,
431 max_length: header.max_length,
432 min_length: header.min_length,
433 pattern: header.pattern,
434 max_items: header.max_items,
435 min_items: header.min_items,
436 unique_items: header.unique_items,
437 r#enum: header.r#enum,
438 r#type: Some(Either::Left(header.r#type)),
439 items: Box::new(items),
440 format: header.format,
441 default: header.default,
442 ..Default::default()
443 };
444
445 core::Header {
448 schema: Some(core::MayBeRef::Value(schema)),
449 description: header.description,
450 required: None,
451 deprecated: None,
452 allow_empty_value: None,
453 style: None,
454 explode,
455 allow_reserved: None,
456 examples: None,
457 content: None,
458 custom_fields: Default::default(),
459 }
460}
461
462fn convert_parameter_ref(
463 parameter_ref: MayBeRef200<Parameter>,
464) -> core::MayBeRef<core::Parameter> {
465 match parameter_ref {
466 MayBeRef200::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
467 reference: value
468 .reference
469 .replace("#/parameters", "#/components/parameters"),
470 }),
471 MayBeRef200::Value(parameter) => {
472 core::MayBeRef::Value(convert_parameter(parameter))
473 }
474 }
475}
476
477fn convert_parameter(parameter: Parameter) -> core::Parameter {
478 let schema = if let Some(schema) = parameter.schema.map(convert_schema_ref)
479 {
480 Some(schema)
481 } else {
482 let all_of = parameter.all_of.map(|all_of| {
483 all_of.into_iter().map(convert_schema_ref).collect()
484 });
485
486 let one_of = parameter.one_of.map(|one_of| {
487 one_of.into_iter().map(convert_schema_ref).collect()
488 });
489
490 let any_of = parameter.any_of.map(|any_of| {
491 any_of.into_iter().map(convert_schema_ref).collect()
492 });
493
494 let not = parameter
495 .not
496 .map(|not| not.into_iter().map(convert_schema_ref).collect());
497
498 let schema = core::Schema {
499 title: None,
500 multiple_of: None,
501 maximum: parameter.maximum,
502 exclusive_maximum: parameter.exclusive_maximum,
503 minimum: parameter.minimum,
504 exclusive_minimum: parameter.exclusive_minimum,
505 max_length: parameter.max_length,
506 min_length: parameter.min_length,
507 pattern: parameter.pattern,
508 max_items: parameter.max_items,
509 min_items: parameter.min_items,
510 unique_items: parameter.unique_items,
511 max_properties: None,
512 min_properties: None,
513 required: None,
514 r#enum: parameter.r#enum,
515 r#type: parameter.r#type,
516
517 all_of,
518 one_of,
519 any_of,
520 not,
521
522 items: Box::new(parameter.items.map(convert_schema_ref)),
523 properties: None,
524 additional_properties: None,
525 description: None,
526 format: parameter.format,
527 default: parameter.default,
528 discriminator: None,
529 read_only: None,
530 write_only: None,
531 xml: None,
532 external_docs: None,
533 example: None,
534 deprecated: None,
535 custom_fields: Default::default(),
536 };
537
538 Some(core::MayBeRef::Value(schema))
539 };
540
541 core::Parameter {
544 name: parameter.name,
545 r#in: parameter.r#in,
546 description: parameter.description,
547 required: parameter.required,
548 deprecated: None,
549 allow_empty_value: parameter.allow_empty_value,
550 style: None,
551 explode: None,
552 allow_reserved: None,
553 schema,
554 examples: None,
555 content: None,
556 custom_fields: parameter.custom_fields,
557 }
558}
559
560fn convert_schema_ref(
561 schema_ref: MayBeRef200<Schema>,
562) -> core::MayBeRef<core::Schema> {
563 match schema_ref {
564 MayBeRef200::Ref(value) => core::MayBeRef::Ref(core::HttpSchemaRef {
565 reference: value
566 .reference
567 .replace("#/definitions", "#/components/schemas"),
568 }),
569 MayBeRef200::Value(schema) => {
570 core::MayBeRef::Value(convert_schema(schema))
571 }
572 }
573}
574
575fn convert_schema(schema: Schema) -> core::Schema {
576 let all_of = schema
577 .all_of
578 .map(|all_of| all_of.into_iter().map(convert_schema_ref).collect());
579
580 let one_of = schema
581 .one_of
582 .map(|one_of| one_of.into_iter().map(convert_schema_ref).collect());
583
584 let any_of = schema
585 .any_of
586 .map(|any_of| any_of.into_iter().map(convert_schema_ref).collect());
587
588 let not = schema
589 .not
590 .map(|not| not.into_iter().map(convert_schema_ref).collect());
591
592 let properties = schema.properties.map(|properties| {
593 properties
594 .into_iter()
595 .map(|(name, schema)| (name, convert_schema_ref(schema)))
596 .collect()
597 });
598
599 let additional_properties =
600 schema.additional_properties.map(|additional_properties| {
601 match additional_properties {
602 Either::Left(value) => Either::Left(value),
603 Either::Right(schema_ref) => {
604 Either::Right(Box::new(convert_schema_ref(*schema_ref)))
605 }
606 }
607 });
608
609 let discriminator =
610 schema
611 .discriminator
612 .map(|discriminator| core::Discriminator {
613 property_name: Some(discriminator),
614 mapping: None,
615 });
616
617 let items = schema.items.map(convert_schema_ref);
618
619 let external_docs = schema.external_docs.map(convert_external_docs);
620
621 core::Schema {
622 title: schema.title,
623 multiple_of: schema.multiple_of,
624 maximum: schema.maximum,
625 exclusive_maximum: schema.exclusive_maximum,
626 minimum: schema.minimum,
627 exclusive_minimum: schema.exclusive_minimum,
628 max_length: schema.max_length,
629 min_length: schema.min_length,
630 pattern: schema.pattern,
631 max_items: schema.max_items,
632 min_items: schema.min_items,
633 unique_items: schema.unique_items,
634 max_properties: schema.max_properties,
635 min_properties: schema.min_properties,
636 required: schema.required,
637 r#enum: schema.r#enum,
638 r#type: schema.r#type,
639 all_of,
640 one_of,
641 any_of,
642 not,
643 items: Box::new(items),
644 properties,
645 additional_properties,
646 description: schema.description,
647 format: schema.format,
648 default: schema.default,
649 discriminator,
650 read_only: schema.read_only,
651 write_only: None,
652 xml: None,
653 external_docs,
654 example: schema.example,
655 deprecated: None,
656 custom_fields: schema.custom_fields,
657 }
658}
659
660fn convert_external_docs(external_docs: ExternalDoc) -> core::ExternalDoc {
661 core::ExternalDoc {
662 url: Some(external_docs.url),
663 description: external_docs.description,
664 }
665}
666
667#[cfg(test)]
668mod tests {
669 #[test]
673 fn test_converter() {
674 }
683}