1use serde_json::{json, Map, Value};
8
9pub fn convert_swagger_to_openapi3(swagger: &Value) -> Result<Value, String> {
19 if swagger.get("swagger").and_then(|v| v.as_str()) != Some("2.0") {
21 return Err("Not a Swagger 2.0 specification".to_string());
22 }
23
24 let mut openapi = Map::new();
25
26 openapi.insert("openapi".to_string(), json!("3.0.3"));
28
29 if let Some(info) = swagger.get("info") {
31 openapi.insert("info".to_string(), info.clone());
32 }
33
34 let servers = convert_servers(swagger);
36 if !servers.is_empty() {
37 openapi.insert("servers".to_string(), json!(servers));
38 }
39
40 if let Some(tags) = swagger.get("tags") {
42 openapi.insert("tags".to_string(), tags.clone());
43 }
44
45 if let Some(paths) = swagger.get("paths") {
47 let global_consumes =
48 swagger.get("consumes").and_then(|v| v.as_array()).cloned().unwrap_or_default();
49 let global_produces =
50 swagger.get("produces").and_then(|v| v.as_array()).cloned().unwrap_or_default();
51
52 let converted_paths = convert_paths(paths, &global_consumes, &global_produces);
53 openapi.insert("paths".to_string(), converted_paths);
54 }
55
56 let mut components = Map::new();
58
59 if let Some(definitions) = swagger.get("definitions") {
61 components.insert("schemas".to_string(), definitions.clone());
62 }
63
64 if let Some(security_defs) = swagger.get("securityDefinitions") {
66 let converted = convert_security_definitions(security_defs);
67 components.insert("securitySchemes".to_string(), converted);
68 }
69
70 if !components.is_empty() {
71 openapi.insert("components".to_string(), json!(components));
72 }
73
74 if let Some(security) = swagger.get("security") {
76 openapi.insert("security".to_string(), security.clone());
77 }
78
79 if let Some(external_docs) = swagger.get("externalDocs") {
81 openapi.insert("externalDocs".to_string(), external_docs.clone());
82 }
83
84 Ok(Value::Object(openapi))
85}
86
87fn convert_servers(swagger: &Value) -> Vec<Value> {
89 let host = swagger.get("host").and_then(|v| v.as_str());
90 let base_path = swagger.get("basePath").and_then(|v| v.as_str()).unwrap_or("");
91 let schemes = swagger
92 .get("schemes")
93 .and_then(|v| v.as_array())
94 .cloned()
95 .unwrap_or_else(|| vec![json!("https")]);
96
97 if let Some(host) = host {
98 schemes
99 .iter()
100 .filter_map(|scheme| {
101 scheme.as_str().map(|s| {
102 json!({
103 "url": format!("{}://{}{}", s, host, base_path)
104 })
105 })
106 })
107 .collect()
108 } else {
109 vec![json!({
111 "url": base_path
112 })]
113 }
114}
115
116fn convert_paths(paths: &Value, global_consumes: &[Value], global_produces: &[Value]) -> Value {
118 let Some(paths_obj) = paths.as_object() else {
119 return paths.clone();
120 };
121
122 let mut converted = Map::new();
123
124 for (path, path_item) in paths_obj {
125 if let Some(path_item_obj) = path_item.as_object() {
126 let converted_path_item =
127 convert_path_item(path_item_obj, global_consumes, global_produces);
128 converted.insert(path.clone(), Value::Object(converted_path_item));
129 }
130 }
131
132 Value::Object(converted)
133}
134
135fn convert_path_item(
137 path_item: &Map<String, Value>,
138 global_consumes: &[Value],
139 global_produces: &[Value],
140) -> Map<String, Value> {
141 let mut converted = Map::new();
142
143 for (key, value) in path_item {
144 match key.as_str() {
145 "get" | "post" | "put" | "delete" | "patch" | "head" | "options" => {
146 if let Some(op) = value.as_object() {
147 let converted_op = convert_operation(op, global_consumes, global_produces);
148 converted.insert(key.clone(), Value::Object(converted_op));
149 }
150 }
151 "parameters" => {
152 if let Some(params) = value.as_array() {
154 let converted_params: Vec<Value> =
155 params.iter().map(convert_parameter).collect();
156 converted.insert(key.clone(), json!(converted_params));
157 }
158 }
159 "$ref" => {
160 if let Some(ref_str) = value.as_str() {
162 converted.insert(key.clone(), json!(convert_ref(ref_str)));
163 }
164 }
165 _ => {
166 converted.insert(key.clone(), value.clone());
168 }
169 }
170 }
171
172 converted
173}
174
175fn convert_operation(
177 operation: &Map<String, Value>,
178 global_consumes: &[Value],
179 global_produces: &[Value],
180) -> Map<String, Value> {
181 let mut converted = Map::new();
182
183 let consumes: Vec<String> = operation
185 .get("consumes")
186 .and_then(|v| v.as_array())
187 .map(|arr| arr.iter())
188 .unwrap_or_else(|| global_consumes.iter())
189 .filter_map(|v| v.as_str().map(String::from))
190 .collect::<Vec<_>>();
191 let consumes = if consumes.is_empty() {
192 vec!["application/json".to_string()]
193 } else {
194 consumes
195 };
196
197 let produces: Vec<String> = operation
198 .get("produces")
199 .and_then(|v| v.as_array())
200 .map(|arr| arr.iter())
201 .unwrap_or_else(|| global_produces.iter())
202 .filter_map(|v| v.as_str().map(String::from))
203 .collect::<Vec<_>>();
204 let produces = if produces.is_empty() {
205 vec!["application/json".to_string()]
206 } else {
207 produces
208 };
209
210 let mut non_body_params = Vec::new();
212 let mut body_param: Option<&Value> = None;
213 let mut form_data_params = Vec::new();
214
215 if let Some(params) = operation.get("parameters").and_then(|v| v.as_array()) {
216 for param in params {
217 match param.get("in").and_then(|v| v.as_str()) {
218 Some("body") => body_param = Some(param),
219 Some("formData") => form_data_params.push(param),
220 _ => non_body_params.push(convert_parameter(param)),
221 }
222 }
223 }
224
225 if !non_body_params.is_empty() {
227 converted.insert("parameters".to_string(), json!(non_body_params));
228 }
229
230 if let Some(body) = body_param {
232 let request_body = convert_body_to_request_body(body, &consumes);
233 converted.insert("requestBody".to_string(), request_body);
234 } else if !form_data_params.is_empty() {
235 let mut properties = serde_json::Map::new();
237 let mut required = Vec::new();
238 let mut has_file = false;
239 for param in &form_data_params {
240 if let Some(name) = param.get("name").and_then(|v| v.as_str()) {
241 let mut prop = serde_json::Map::new();
242 if let Some(typ) = param.get("type").and_then(|v| v.as_str()) {
243 if typ == "file" {
244 prop.insert("type".to_string(), json!("string"));
245 prop.insert("format".to_string(), json!("binary"));
246 has_file = true;
247 } else {
248 prop.insert("type".to_string(), json!(typ));
249 }
250 }
251 if let Some(desc) = param.get("description") {
252 prop.insert("description".to_string(), desc.clone());
253 }
254 properties.insert(name.to_string(), json!(prop));
255 if param.get("required").and_then(|v| v.as_bool()).unwrap_or(false) {
256 required.push(json!(name));
257 }
258 }
259 }
260 let content_type = if has_file {
261 "multipart/form-data"
262 } else {
263 "application/x-www-form-urlencoded"
264 };
265 let mut schema = serde_json::Map::new();
266 schema.insert("type".to_string(), json!("object"));
267 schema.insert("properties".to_string(), json!(properties));
268 if !required.is_empty() {
269 schema.insert("required".to_string(), json!(required));
270 }
271 converted.insert(
272 "requestBody".to_string(),
273 json!({
274 "content": {
275 content_type: {
276 "schema": schema
277 }
278 }
279 }),
280 );
281 }
282
283 if let Some(responses) = operation.get("responses") {
285 let converted_responses = convert_responses(responses, &produces);
286 converted.insert("responses".to_string(), converted_responses);
287 }
288
289 for (key, value) in operation {
291 match key.as_str() {
292 "parameters" | "responses" | "consumes" | "produces" => {
293 }
295 _ => {
296 converted.insert(key.clone(), value.clone());
297 }
298 }
299 }
300
301 converted
302}
303
304fn convert_parameter(param: &Value) -> Value {
306 let Some(param_obj) = param.as_object() else {
307 return param.clone();
308 };
309
310 let mut converted = Map::new();
311
312 for key in &["name", "in", "description", "required", "allowEmptyValue"] {
314 if let Some(value) = param_obj.get(*key) {
315 converted.insert(key.to_string(), value.clone());
316 }
317 }
318
319 let param_in = param_obj.get("in").and_then(|v| v.as_str());
321
322 if param_in == Some("body") || param_in == Some("formData") {
324 return param.clone();
325 }
326
327 let mut schema = Map::new();
329
330 if let Some(param_type) = param_obj.get("type") {
331 schema.insert("type".to_string(), param_type.clone());
332 }
333 if let Some(format) = param_obj.get("format") {
334 schema.insert("format".to_string(), format.clone());
335 }
336 if let Some(items) = param_obj.get("items") {
337 schema.insert("items".to_string(), items.clone());
338 }
339 if let Some(enum_values) = param_obj.get("enum") {
340 schema.insert("enum".to_string(), enum_values.clone());
341 }
342 if let Some(default) = param_obj.get("default") {
343 schema.insert("default".to_string(), default.clone());
344 }
345 if let Some(minimum) = param_obj.get("minimum") {
346 schema.insert("minimum".to_string(), minimum.clone());
347 }
348 if let Some(maximum) = param_obj.get("maximum") {
349 schema.insert("maximum".to_string(), maximum.clone());
350 }
351 if let Some(pattern) = param_obj.get("pattern") {
352 schema.insert("pattern".to_string(), pattern.clone());
353 }
354
355 if !schema.is_empty() {
356 converted.insert("schema".to_string(), Value::Object(schema));
357 }
358
359 Value::Object(converted)
360}
361
362fn convert_body_to_request_body(body: &Value, consumes: &[String]) -> Value {
364 let mut request_body = Map::new();
365
366 if let Some(desc) = body.get("description") {
367 request_body.insert("description".to_string(), desc.clone());
368 }
369
370 if let Some(required) = body.get("required") {
371 request_body.insert("required".to_string(), required.clone());
372 }
373
374 let mut content = Map::new();
376 let schema = body.get("schema").cloned().unwrap_or(json!({}));
377
378 for media_type in consumes {
379 content.insert(
380 media_type.clone(),
381 json!({
382 "schema": convert_schema_refs(&schema)
383 }),
384 );
385 }
386
387 request_body.insert("content".to_string(), Value::Object(content));
388
389 Value::Object(request_body)
390}
391
392fn convert_responses(responses: &Value, produces: &[String]) -> Value {
394 let Some(responses_obj) = responses.as_object() else {
395 return responses.clone();
396 };
397
398 let mut converted = Map::new();
399
400 for (status_code, response) in responses_obj {
401 if let Some(response_obj) = response.as_object() {
402 let converted_response = convert_response(response_obj, produces);
403 converted.insert(status_code.clone(), Value::Object(converted_response));
404 }
405 }
406
407 Value::Object(converted)
408}
409
410fn convert_response(response: &Map<String, Value>, produces: &[String]) -> Map<String, Value> {
412 let mut converted = Map::new();
413
414 if let Some(desc) = response.get("description") {
416 converted.insert("description".to_string(), desc.clone());
417 } else {
418 converted.insert("description".to_string(), json!("Response"));
419 }
420
421 if let Some(schema) = response.get("schema") {
423 let mut content = Map::new();
424 for media_type in produces {
425 content.insert(
426 media_type.clone(),
427 json!({
428 "schema": convert_schema_refs(schema)
429 }),
430 );
431 }
432 converted.insert("content".to_string(), Value::Object(content));
433 }
434
435 if let Some(headers) = response.get("headers") {
437 if let Some(headers_obj) = headers.as_object() {
438 let mut converted_headers = Map::new();
439 for (name, header) in headers_obj {
440 converted_headers.insert(name.clone(), convert_header(header));
441 }
442 converted.insert("headers".to_string(), Value::Object(converted_headers));
443 }
444 }
445
446 if let Some(examples) = response.get("examples") {
448 converted.insert("examples".to_string(), examples.clone());
449 }
450
451 converted
452}
453
454fn convert_header(header: &Value) -> Value {
456 let Some(header_obj) = header.as_object() else {
457 return header.clone();
458 };
459
460 let mut converted = Map::new();
461
462 if let Some(desc) = header_obj.get("description") {
463 converted.insert("description".to_string(), desc.clone());
464 }
465
466 let mut schema = Map::new();
468 if let Some(header_type) = header_obj.get("type") {
469 schema.insert("type".to_string(), header_type.clone());
470 }
471 if let Some(format) = header_obj.get("format") {
472 schema.insert("format".to_string(), format.clone());
473 }
474
475 if !schema.is_empty() {
476 converted.insert("schema".to_string(), Value::Object(schema));
477 }
478
479 Value::Object(converted)
480}
481
482fn convert_security_definitions(security_defs: &Value) -> Value {
484 let Some(defs_obj) = security_defs.as_object() else {
485 return security_defs.clone();
486 };
487
488 let mut converted = Map::new();
489
490 for (name, def) in defs_obj {
491 if let Some(def_obj) = def.as_object() {
492 let converted_def = convert_security_definition(def_obj);
493 converted.insert(name.clone(), Value::Object(converted_def));
494 }
495 }
496
497 Value::Object(converted)
498}
499
500fn convert_security_definition(def: &Map<String, Value>) -> Map<String, Value> {
502 let mut converted = Map::new();
503
504 let security_type = def.get("type").and_then(|v| v.as_str()).unwrap_or("");
505
506 match security_type {
507 "basic" => {
508 converted.insert("type".to_string(), json!("http"));
509 converted.insert("scheme".to_string(), json!("basic"));
510 }
511 "apiKey" => {
512 converted.insert("type".to_string(), json!("apiKey"));
513 if let Some(name) = def.get("name") {
514 converted.insert("name".to_string(), name.clone());
515 }
516 if let Some(in_val) = def.get("in") {
517 converted.insert("in".to_string(), in_val.clone());
518 }
519 }
520 "oauth2" => {
521 converted.insert("type".to_string(), json!("oauth2"));
522
523 let flow = def.get("flow").and_then(|v| v.as_str()).unwrap_or("implicit");
525 let mut flows = Map::new();
526
527 let mut flow_obj = Map::new();
528
529 let flow_name = match flow {
531 "implicit" => "implicit",
532 "password" => "password",
533 "application" => "clientCredentials",
534 "accessCode" => "authorizationCode",
535 _ => "implicit",
536 };
537
538 if let Some(auth_url) = def.get("authorizationUrl") {
539 flow_obj.insert("authorizationUrl".to_string(), auth_url.clone());
540 }
541 if let Some(token_url) = def.get("tokenUrl") {
542 flow_obj.insert("tokenUrl".to_string(), token_url.clone());
543 }
544 if let Some(scopes) = def.get("scopes") {
545 flow_obj.insert("scopes".to_string(), scopes.clone());
546 } else {
547 flow_obj.insert("scopes".to_string(), json!({}));
548 }
549
550 flows.insert(flow_name.to_string(), Value::Object(flow_obj));
551 converted.insert("flows".to_string(), Value::Object(flows));
552 }
553 _ => {
554 for (key, value) in def {
556 converted.insert(key.clone(), value.clone());
557 }
558 }
559 }
560
561 if let Some(desc) = def.get("description") {
563 converted.insert("description".to_string(), desc.clone());
564 }
565
566 converted
567}
568
569fn convert_ref(ref_str: &str) -> String {
571 if let Some(name) = ref_str.strip_prefix("#/definitions/") {
573 format!("#/components/schemas/{}", name)
574 } else {
575 ref_str.to_string()
576 }
577}
578
579fn convert_schema_refs(schema: &Value) -> Value {
581 match schema {
582 Value::Object(obj) => {
583 let mut converted = Map::new();
584 for (key, value) in obj {
585 if key == "$ref" {
586 if let Some(ref_str) = value.as_str() {
587 converted.insert(key.clone(), json!(convert_ref(ref_str)));
588 } else {
589 converted.insert(key.clone(), value.clone());
590 }
591 } else {
592 converted.insert(key.clone(), convert_schema_refs(value));
593 }
594 }
595 Value::Object(converted)
596 }
597 Value::Array(arr) => Value::Array(arr.iter().map(convert_schema_refs).collect()),
598 _ => schema.clone(),
599 }
600}
601
602pub fn is_swagger_2(value: &Value) -> bool {
604 value.get("swagger").and_then(|v| v.as_str()) == Some("2.0")
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610
611 #[test]
612 fn test_is_swagger_2() {
613 assert!(is_swagger_2(&json!({"swagger": "2.0"})));
614 assert!(!is_swagger_2(&json!({"openapi": "3.0.0"})));
615 }
616
617 #[test]
618 fn test_convert_servers() {
619 let swagger = json!({
620 "swagger": "2.0",
621 "host": "api.example.com",
622 "basePath": "/v1",
623 "schemes": ["https", "http"]
624 });
625
626 let servers = convert_servers(&swagger);
627 assert_eq!(servers.len(), 2);
628 assert_eq!(servers[0]["url"], "https://api.example.com/v1");
629 assert_eq!(servers[1]["url"], "http://api.example.com/v1");
630 }
631
632 #[test]
633 fn test_convert_ref() {
634 assert_eq!(convert_ref("#/definitions/User"), "#/components/schemas/User");
635 assert_eq!(convert_ref("#/components/schemas/User"), "#/components/schemas/User");
636 }
637
638 #[test]
639 fn test_convert_parameter() {
640 let param = json!({
641 "name": "userId",
642 "in": "path",
643 "required": true,
644 "type": "string",
645 "format": "uuid"
646 });
647
648 let converted = convert_parameter(¶m);
649 assert_eq!(converted["name"], "userId");
650 assert_eq!(converted["in"], "path");
651 assert_eq!(converted["schema"]["type"], "string");
652 assert_eq!(converted["schema"]["format"], "uuid");
653 }
654
655 #[test]
656 fn test_convert_security_basic() {
657 let def = json!({
658 "type": "basic",
659 "description": "Basic auth"
660 });
661
662 if let Some(def_obj) = def.as_object() {
663 let converted = convert_security_definition(def_obj);
664 assert_eq!(converted["type"], json!("http"));
665 assert_eq!(converted["scheme"], json!("basic"));
666 }
667 }
668
669 #[test]
670 fn test_basic_conversion() {
671 let swagger = json!({
672 "swagger": "2.0",
673 "info": {
674 "title": "Test API",
675 "version": "1.0.0"
676 },
677 "host": "api.example.com",
678 "basePath": "/v1",
679 "schemes": ["https"],
680 "paths": {
681 "/users": {
682 "get": {
683 "operationId": "getUsers",
684 "produces": ["application/json"],
685 "responses": {
686 "200": {
687 "description": "Success"
688 }
689 }
690 }
691 }
692 }
693 });
694
695 let result = convert_swagger_to_openapi3(&swagger).unwrap();
696 assert_eq!(result["openapi"], "3.0.3");
697 assert_eq!(result["info"]["title"], "Test API");
698 assert!(result["servers"].as_array().is_some());
699 assert!(result["paths"]["/users"]["get"].is_object());
700 }
701}