1use super::schema as v2;
2use crate as v3;
3use crate::{Parameter, StatusCode};
4use indexmap::IndexMap;
5use std::convert::TryInto;
6
7trait TryRemove<T> {
8 fn try_remove(&mut self, i: usize) -> Option<T>;
9}
10
11impl<T> TryRemove<T> for Vec<T> {
12 fn try_remove(&mut self, i: usize) -> Option<T> {
13 self.get(i)?;
14 Some(self.remove(i))
15 }
16}
17
18impl Into<v3::OpenAPI> for v2::OpenAPI {
19 fn into(self) -> v3::OpenAPI {
20 let v2::OpenAPI {
21 swagger: _,
22 info,
23 host,
24 base_path,
25 schemes,
26 consumes: _,
27 produces: _,
28 paths,
29 definitions,
30 parameters,
31 responses,
32 security_definitions,
33 security,
34 tags,
35 external_docs,
36 extensions,
37 } = self;
38 let mut components = v3::Components::default();
39
40 components.schemas = definitions
41 .unwrap_or_default()
42 .into_iter()
43 .map(|(k, v)| (k, v3::RefOr::Item(v.into())))
44 .collect();
45
46 components.parameters = parameters
47 .unwrap_or_default()
48 .into_iter()
49 .filter_map(|(k, v)| {
50 let v: v3::RefOr<v3::Parameter> = v.try_into().ok()?;
51 Some((k, v))
52 })
53 .collect();
54
55 components.responses = responses
56 .unwrap_or_default()
57 .into_iter()
58 .map(|(k, v)| (k, v.into()))
59 .collect();
60
61 components.security_schemes = security_definitions
62 .unwrap_or_default()
63 .into_iter()
64 .map(|(k, v)| (k, v.into()))
65 .collect();
66
67 v3::OpenAPI {
68 openapi: "3.0.3".to_string(),
69 info: info.into(),
70 servers: host
71 .map(|h| {
72 let scheme = schemes
73 .and_then(|mut s| {
74 if s.len() >= 1 {
75 Some(s.remove(0))
76 } else {
77 None
78 }
79 })
80 .map(|s| s.as_str())
81 .unwrap_or("http");
82 let url = format!("{}://{}{}", scheme, h, base_path.unwrap_or_default());
83 vec![v3::Server {
84 url,
85 ..v3::Server::default()
86 }]
87 })
88 .unwrap_or_default(),
89 paths: paths.into(),
90 components,
91 security: security.unwrap_or_default(),
92 tags: tags
93 .unwrap_or_default()
94 .into_iter()
95 .map(|t| t.into())
96 .collect(),
97 external_docs: external_docs
98 .and_then(|mut e| e.try_remove(0))
99 .map(|e| e.into()),
100 extensions,
101 }
102 }
103}
104
105impl Into<v3::Paths> for IndexMap<String, v2::PathItem> {
106 fn into(self) -> v3::Paths {
107 v3::Paths {
108 paths: self.into_iter().map(|(k, v)| (k, v.into())).collect(),
109 extensions: Default::default(),
110 }
111 }
112}
113
114impl Into<v3::RefOr<v3::PathItem>> for v2::PathItem {
115 fn into(self) -> v3::RefOr<v3::PathItem> {
116 let v2::PathItem {
117 get,
118 put,
119 post,
120 delete,
121 options,
122 head,
123 patch,
124 parameters,
125 extensions,
126 } = self;
127 v3::RefOr::Item(v3::PathItem {
128 summary: None,
129 description: None,
130 get: get.map(|op| op.into()),
131 put: put.map(|op| op.into()),
132 post: post.map(|op| op.into()),
133 delete: delete.map(|op| op.into()),
134 options: options.map(|op| op.into()),
135 head: head.map(|op| op.into()),
136 patch: patch.map(|op| op.into()),
137 trace: None,
138 servers: vec![],
139 parameters: parameters
140 .unwrap_or_default()
141 .into_iter()
142 .flat_map(|p| p.try_into().ok())
143 .collect(),
144 extensions,
145 })
146 }
147}
148
149fn rewrite_ref(s: &str) -> String {
151 s.replace("#/definitions/", "#/components/schemas/")
152}
153
154fn build_schema_kind(type_: &str, format: Option<String>) -> v3::SchemaKind {
155 match type_ {
156 "string" => v3::SchemaKind::Type(v3::Type::String(v3::StringType {
157 format: {
158 let s = serde_json::to_string(&format).unwrap();
159 serde_json::from_str(&s).unwrap()
160 },
161 ..v3::StringType::default()
162 })),
163 "number" => v3::SchemaKind::Type(v3::Type::Number(v3::NumberType {
164 format: {
165 let s = serde_json::to_string(&format).unwrap();
166 serde_json::from_str(&s).unwrap()
167 },
168 ..v3::NumberType::default()
169 })),
170 "integer" => v3::SchemaKind::Type(v3::Type::Integer(v3::IntegerType {
171 format: {
172 let s = serde_json::to_string(&format).unwrap();
173 serde_json::from_str(&s).unwrap()
174 },
175 ..v3::IntegerType::default()
176 })),
177 "boolean" => v3::SchemaKind::Type(v3::Type::Boolean {}),
178 "array" => v3::SchemaKind::Type(v3::Type::Array(v3::ArrayType {
179 ..v3::ArrayType::default()
180 })),
181 "object" => {
182 let object_type = v3::ObjectType::default();
183 v3::SchemaKind::Type(v3::Type::Object(object_type))
184 }
185 _ => panic!("Unknown schema type: {}", type_),
186 }
187}
188
189impl Into<v3::Schema> for v2::Schema {
190 fn into(self) -> v3::Schema {
191 let v2::Schema {
192 description,
193 schema_type,
194 format,
195 enum_values,
196 required,
197 items,
198 properties,
199 all_of,
200 other,
201 } = self;
202
203 let schema_data = v3::SchemaData {
204 description,
205 extensions: other,
206 ..v3::SchemaData::default()
207 };
208
209 if let Some(all_of) = all_of {
210 return v3::Schema {
211 data: schema_data,
212 kind: v3::SchemaKind::AllOf {
213 all_of: all_of.into_iter().map(|s| s.into()).collect(),
214 },
215 };
216 }
217
218 let schema_type = schema_type.unwrap_or_else(|| "object".to_string());
219 let mut schema_kind = build_schema_kind(&schema_type, format);
220
221 match &mut schema_kind {
222 v3::SchemaKind::Type(v3::Type::String(ref mut s)) => {
223 s.enumeration = enum_values.unwrap_or_default();
224 }
225 v3::SchemaKind::Type(v3::Type::Object(ref mut o)) => {
226 if let Some(properties) = properties {
227 o.properties = properties.into_iter().map(|(k, v)| (k, v.into())).collect();
228 }
229 o.required = required.unwrap_or_default();
230 }
231 v3::SchemaKind::Type(v3::Type::Array(ref mut a)) => {
232 a.items = Some({
233 let item = items.unwrap();
234 let item = *item;
235 let item: v3::RefOr<v3::Schema> = item.into();
236 item.boxed()
237 });
238 }
239 _ => {}
240 }
241
242 v3::Schema {
243 data: schema_data,
244 kind: schema_kind,
245 }
246 }
247}
248
249impl TryInto<v3::RefOr<v3::Parameter>> for v2::Parameter {
250 type Error = anyhow::Error;
251
252 fn try_into(self) -> Result<v3::RefOr<v3::Parameter>, Self::Error> {
253 if !self.valid_v3_location() {
254 return Err(anyhow::anyhow!(
255 "Invalid location: {}",
256 serde_json::to_string(&self.location).unwrap()
257 ));
258 }
259 let v2::Parameter {
260 name,
261 location,
262 description,
263 required,
264 schema: _,
265 type_,
266 format,
267 items,
268 default,
269 unique_items,
270 collection_format,
271 } = self;
272 let type_ = type_.unwrap();
273
274 let mut kind = build_schema_kind(&type_, format);
275 let mut data = v3::SchemaData::default();
276
277 match &mut kind {
278 v3::SchemaKind::Type(v3::Type::Array(ref mut a)) => {
279 a.items = items.map(|item| {
280 let item: v3::RefOr<v3::Schema> = item.into();
281 item.boxed()
282 });
283 a.unique_items = unique_items.unwrap_or_default();
284 }
285 _ => {}
286 }
287 data.default = default;
288
289 let mut explode = None;
290 if let Some(collection_format) = collection_format {
291 match collection_format.as_str() {
292 "multi" => explode = Some(true),
293 "csv" => explode = Some(false),
294 _ => {}
295 }
296 }
297
298 let schema = v3::Schema { data, kind };
299 let data = v3::ParameterData {
300 name,
301 description,
302 required: required.unwrap_or_default(),
303 deprecated: None,
304 format: v3::ParameterSchemaOrContent::Schema(schema.into()),
305 example: None,
306 examples: Default::default(),
307 explode,
308 extensions: Default::default(),
309 };
310 let kind = match location {
311 v2::ParameterLocation::Query => v3::ParameterKind::Query {
312 allow_reserved: false,
313 style: Default::default(),
314 allow_empty_value: None,
315 },
316 v2::ParameterLocation::Header => v3::ParameterKind::Header {
317 style: Default::default(),
318 },
319 v2::ParameterLocation::Path => v3::ParameterKind::Path {
320 style: Default::default(),
321 },
322 v2::ParameterLocation::FormData | v2::ParameterLocation::Body => {
323 panic!("Invalid location")
324 }
325 };
326 let parameter = Parameter { data, kind };
327 Ok(v3::RefOr::Item(parameter))
328 }
329}
330
331fn split_params_into_params_and_body(
332 params: Option<Vec<v2::Parameter>>,
333) -> (Vec<v2::Parameter>, Vec<v2::Parameter>) {
334 params
335 .unwrap_or_default()
336 .into_iter()
337 .partition(|p| p.valid_v3_location())
338}
339
340impl Into<v3::Operation> for v2::Operation {
341 fn into(self) -> v3::Operation {
342 let v2::Operation {
343 consumes: _,
344 produces: _,
345 schemes: _,
346 tags,
347 summary,
348 description,
349 operation_id,
350 parameters,
351 mut responses,
352 security,
353 extensions,
354 } = self;
355 let (parameters, body) = split_params_into_params_and_body(parameters);
356 let body = body.into();
357
358 let responses = {
359 let mut r = v3::Responses::default();
360 r.default = responses.swap_remove("default").map(|r| r.into());
361 r.responses = responses
362 .into_iter()
363 .map(|(k, v)| {
364 (
365 StatusCode::Code(
366 k.parse::<u16>()
367 .expect(&format!("Invalid status code: {}", k)),
368 ),
369 v.into(),
370 )
371 })
372 .collect();
373 r
374 };
375 v3::Operation {
376 tags: tags.unwrap_or_default(),
377 summary,
378 description,
379 external_docs: None,
380 operation_id,
381 parameters: parameters
382 .into_iter()
383 .flat_map(|p| p.try_into().ok())
384 .collect(),
385 request_body: Some(v3::RefOr::Item(body)),
386 responses,
387 deprecated: false,
388 security,
389 servers: vec![],
390 extensions,
391 }
392 }
393}
394
395impl Into<v3::RefOr<v3::Schema>> for v2::ReferenceOrSchema {
396 fn into(self) -> v3::RefOr<v3::Schema> {
397 match self {
398 v2::ReferenceOrSchema::Item(s) => v3::RefOr::Item(s.into()),
399 v2::ReferenceOrSchema::Reference { reference } => v3::RefOr::Reference {
400 reference: rewrite_ref(&reference),
401 },
402 }
403 }
404}
405
406impl Into<v3::RequestBody> for Vec<v2::Parameter> {
407 fn into(self) -> v3::RequestBody {
408 let mut object = v3::ObjectType::default();
409 for param in self {
410 let v2::Parameter {
411 name,
412 location,
413 description: _,
414 required,
415 schema,
416 type_: _,
417 format: _,
418 items: _,
419 default: _,
420 unique_items: _,
421 collection_format: _,
422 } = param;
423 assert!(location == v2::ParameterLocation::Body);
424 if required.unwrap_or_default() {
425 object.required.push(name.clone());
426 }
427 let schema = match schema {
428 Some(s) => s.into(),
429 None => v3::RefOr::Item(v3::Schema::new_any()),
430 };
431 object.properties.insert(name, schema);
432 }
433
434 let mut content = IndexMap::new();
435 content.insert(
436 "application/json".to_string(),
437 v3::MediaType {
438 schema: Some(v3::RefOr::Item(v3::Schema {
439 data: v3::SchemaData::default(),
440 kind: v3::SchemaKind::Type(v3::Type::Object(object)),
441 })),
442 ..v3::MediaType::default()
443 },
444 );
445 v3::RequestBody {
446 description: None,
447 content,
448 required: true,
449 extensions: Default::default(),
450 }
451 }
452}
453
454impl Into<v3::ExternalDocumentation> for v2::ExternalDoc {
455 fn into(self) -> v3::ExternalDocumentation {
456 let v2::ExternalDoc { description, url } = self;
457 v3::ExternalDocumentation {
458 description,
459 url,
460 ..v3::ExternalDocumentation::default()
461 }
462 }
463}
464
465impl Into<v3::Tag> for v2::Tag {
466 fn into(self) -> v3::Tag {
467 let v2::Tag {
468 name,
469 description,
470 external_docs,
471 } = self;
472 v3::Tag {
473 name,
474 description,
475 external_docs: external_docs
476 .and_then(|mut e| e.try_remove(0))
477 .map(|e| e.into()),
478 extensions: Default::default(),
479 }
480 }
481}
482
483impl Into<v3::Info> for v2::Info {
484 fn into(self) -> v3::Info {
485 let v2::Info {
486 title,
487 description,
488 terms_of_service,
489 contact,
490 license,
491 version,
492 } = self;
493 v3::Info {
494 title: title.unwrap_or_default(),
495 description,
496 terms_of_service,
497 contact: contact.map(|c| c.into()),
498 license: license.map(|l| l.into()),
499 version: version.unwrap_or_else(|| "0.1.0".to_string()),
500 extensions: Default::default(),
501 }
502 }
503}
504
505impl Into<v3::Contact> for v2::Contact {
506 fn into(self) -> v3::Contact {
507 let v2::Contact { name, url, email } = self;
508 v3::Contact {
509 name,
510 url,
511 email,
512 extensions: Default::default(),
513 }
514 }
515}
516
517impl Into<v3::License> for v2::License {
518 fn into(self) -> v3::License {
519 let v2::License { name, url } = self;
520 v3::License {
521 name: name.unwrap_or_default(),
522 url,
523 extensions: Default::default(),
524 }
525 }
526}
527
528impl Into<v3::RefOr<v3::SecurityScheme>> for v2::Security {
529 fn into(self) -> v3::RefOr<v3::SecurityScheme> {
530 match self {
531 v2::Security::ApiKey {
532 name,
533 location,
534 description,
535 } => {
536 let location = match location {
537 v2::ApiKeyLocation::Query => v3::APIKeyLocation::Query,
538 v2::ApiKeyLocation::Header => v3::APIKeyLocation::Header,
539 };
540 v3::RefOr::Item(v3::SecurityScheme::APIKey {
541 location,
542 name,
543 description,
544 })
545 }
546 v2::Security::Basic { description } => v3::RefOr::Item(v3::SecurityScheme::HTTP {
547 scheme: "basic".to_string(),
548 bearer_format: None,
549 description,
550 }),
551 v2::Security::Oauth2 {
552 flow,
553 authorization_url,
554 token_url,
555 scopes,
556 description,
557 } => {
558 let mut implicit = None;
559 let mut password = None;
560 let mut client_credentials = None;
561 let mut authorization_code = None;
562 match flow {
563 v2::Flow::AccessCode => {
564 authorization_code = Some(v3::AuthCodeOAuth2Flow {
565 authorization_url,
566 token_url: token_url.unwrap(),
567 refresh_url: None,
568 scopes,
569 });
570 }
571 v2::Flow::Application => {
572 client_credentials = Some(v3::OAuth2Flow {
573 token_url: token_url.unwrap(),
574 refresh_url: None,
575 scopes,
576 });
577 }
578 v2::Flow::Implicit => {
579 implicit = Some(v3::ImplicitOAuth2Flow {
580 authorization_url,
581 refresh_url: None,
582 scopes,
583 });
584 }
585 v2::Flow::Password => {
586 password = Some(v3::OAuth2Flow {
587 token_url: token_url.unwrap(),
588 refresh_url: None,
589 scopes,
590 });
591 }
592 }
593 let flows = v3::OAuth2Flows {
594 implicit,
595 password,
596 client_credentials,
597 authorization_code,
598 };
599 v3::RefOr::Item(v3::SecurityScheme::OAuth2 { flows, description })
600 }
601 }
602 }
603}
604
605impl Into<v3::RefOr<v3::Response>> for v2::Response {
606 fn into(self) -> v3::RefOr<v3::Response> {
607 let v2::Response {
608 description,
609 schema,
610 } = self;
611 let Some(schema) = schema else {
612 return v3::RefOr::Item(v3::Response {
613 description,
614 ..v3::Response::default()
615 });
616 };
617 v3::RefOr::Item(v3::Response {
618 description,
619 content: {
620 let mut map = IndexMap::new();
621 map.insert(
622 "application/json".to_string(),
623 v3::MediaType {
624 schema: Some(schema.into()),
625 ..v3::MediaType::default()
626 },
627 );
628 map
629 },
630 ..v3::Response::default()
631 })
632 }
633}