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