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
214 if let Some(params) = operation.get("parameters").and_then(|v| v.as_array()) {
215 for param in params {
216 if param.get("in").and_then(|v| v.as_str()) == Some("body") {
217 body_param = Some(param);
218 } else {
219 non_body_params.push(convert_parameter(param));
220 }
221 }
222 }
223
224 if !non_body_params.is_empty() {
226 converted.insert("parameters".to_string(), json!(non_body_params));
227 }
228
229 if let Some(body) = body_param {
231 let request_body = convert_body_to_request_body(body, &consumes);
232 converted.insert("requestBody".to_string(), request_body);
233 }
234
235 if let Some(responses) = operation.get("responses") {
237 let converted_responses = convert_responses(responses, &produces);
238 converted.insert("responses".to_string(), converted_responses);
239 }
240
241 for (key, value) in operation {
243 match key.as_str() {
244 "parameters" | "responses" | "consumes" | "produces" => {
245 }
247 _ => {
248 converted.insert(key.clone(), value.clone());
249 }
250 }
251 }
252
253 converted
254}
255
256fn convert_parameter(param: &Value) -> Value {
258 let Some(param_obj) = param.as_object() else {
259 return param.clone();
260 };
261
262 let mut converted = Map::new();
263
264 for key in &["name", "in", "description", "required", "allowEmptyValue"] {
266 if let Some(value) = param_obj.get(*key) {
267 converted.insert(key.to_string(), value.clone());
268 }
269 }
270
271 let param_in = param_obj.get("in").and_then(|v| v.as_str());
273
274 if param_in == Some("body") || param_in == Some("formData") {
276 return param.clone();
277 }
278
279 let mut schema = Map::new();
281
282 if let Some(param_type) = param_obj.get("type") {
283 schema.insert("type".to_string(), param_type.clone());
284 }
285 if let Some(format) = param_obj.get("format") {
286 schema.insert("format".to_string(), format.clone());
287 }
288 if let Some(items) = param_obj.get("items") {
289 schema.insert("items".to_string(), items.clone());
290 }
291 if let Some(enum_values) = param_obj.get("enum") {
292 schema.insert("enum".to_string(), enum_values.clone());
293 }
294 if let Some(default) = param_obj.get("default") {
295 schema.insert("default".to_string(), default.clone());
296 }
297 if let Some(minimum) = param_obj.get("minimum") {
298 schema.insert("minimum".to_string(), minimum.clone());
299 }
300 if let Some(maximum) = param_obj.get("maximum") {
301 schema.insert("maximum".to_string(), maximum.clone());
302 }
303 if let Some(pattern) = param_obj.get("pattern") {
304 schema.insert("pattern".to_string(), pattern.clone());
305 }
306
307 if !schema.is_empty() {
308 converted.insert("schema".to_string(), Value::Object(schema));
309 }
310
311 Value::Object(converted)
312}
313
314fn convert_body_to_request_body(body: &Value, consumes: &[String]) -> Value {
316 let mut request_body = Map::new();
317
318 if let Some(desc) = body.get("description") {
319 request_body.insert("description".to_string(), desc.clone());
320 }
321
322 if let Some(required) = body.get("required") {
323 request_body.insert("required".to_string(), required.clone());
324 }
325
326 let mut content = Map::new();
328 let schema = body.get("schema").cloned().unwrap_or(json!({}));
329
330 for media_type in consumes {
331 content.insert(
332 media_type.clone(),
333 json!({
334 "schema": convert_schema_refs(&schema)
335 }),
336 );
337 }
338
339 request_body.insert("content".to_string(), Value::Object(content));
340
341 Value::Object(request_body)
342}
343
344fn convert_responses(responses: &Value, produces: &[String]) -> Value {
346 let Some(responses_obj) = responses.as_object() else {
347 return responses.clone();
348 };
349
350 let mut converted = Map::new();
351
352 for (status_code, response) in responses_obj {
353 if let Some(response_obj) = response.as_object() {
354 let converted_response = convert_response(response_obj, produces);
355 converted.insert(status_code.clone(), Value::Object(converted_response));
356 }
357 }
358
359 Value::Object(converted)
360}
361
362fn convert_response(response: &Map<String, Value>, produces: &[String]) -> Map<String, Value> {
364 let mut converted = Map::new();
365
366 if let Some(desc) = response.get("description") {
368 converted.insert("description".to_string(), desc.clone());
369 } else {
370 converted.insert("description".to_string(), json!("Response"));
371 }
372
373 if let Some(schema) = response.get("schema") {
375 let mut content = Map::new();
376 for media_type in produces {
377 content.insert(
378 media_type.clone(),
379 json!({
380 "schema": convert_schema_refs(schema)
381 }),
382 );
383 }
384 converted.insert("content".to_string(), Value::Object(content));
385 }
386
387 if let Some(headers) = response.get("headers") {
389 if let Some(headers_obj) = headers.as_object() {
390 let mut converted_headers = Map::new();
391 for (name, header) in headers_obj {
392 converted_headers.insert(name.clone(), convert_header(header));
393 }
394 converted.insert("headers".to_string(), Value::Object(converted_headers));
395 }
396 }
397
398 if let Some(examples) = response.get("examples") {
400 converted.insert("examples".to_string(), examples.clone());
401 }
402
403 converted
404}
405
406fn convert_header(header: &Value) -> Value {
408 let Some(header_obj) = header.as_object() else {
409 return header.clone();
410 };
411
412 let mut converted = Map::new();
413
414 if let Some(desc) = header_obj.get("description") {
415 converted.insert("description".to_string(), desc.clone());
416 }
417
418 let mut schema = Map::new();
420 if let Some(header_type) = header_obj.get("type") {
421 schema.insert("type".to_string(), header_type.clone());
422 }
423 if let Some(format) = header_obj.get("format") {
424 schema.insert("format".to_string(), format.clone());
425 }
426
427 if !schema.is_empty() {
428 converted.insert("schema".to_string(), Value::Object(schema));
429 }
430
431 Value::Object(converted)
432}
433
434fn convert_security_definitions(security_defs: &Value) -> Value {
436 let Some(defs_obj) = security_defs.as_object() else {
437 return security_defs.clone();
438 };
439
440 let mut converted = Map::new();
441
442 for (name, def) in defs_obj {
443 if let Some(def_obj) = def.as_object() {
444 let converted_def = convert_security_definition(def_obj);
445 converted.insert(name.clone(), Value::Object(converted_def));
446 }
447 }
448
449 Value::Object(converted)
450}
451
452fn convert_security_definition(def: &Map<String, Value>) -> Map<String, Value> {
454 let mut converted = Map::new();
455
456 let security_type = def.get("type").and_then(|v| v.as_str()).unwrap_or("");
457
458 match security_type {
459 "basic" => {
460 converted.insert("type".to_string(), json!("http"));
461 converted.insert("scheme".to_string(), json!("basic"));
462 }
463 "apiKey" => {
464 converted.insert("type".to_string(), json!("apiKey"));
465 if let Some(name) = def.get("name") {
466 converted.insert("name".to_string(), name.clone());
467 }
468 if let Some(in_val) = def.get("in") {
469 converted.insert("in".to_string(), in_val.clone());
470 }
471 }
472 "oauth2" => {
473 converted.insert("type".to_string(), json!("oauth2"));
474
475 let flow = def.get("flow").and_then(|v| v.as_str()).unwrap_or("implicit");
477 let mut flows = Map::new();
478
479 let mut flow_obj = Map::new();
480
481 let flow_name = match flow {
483 "implicit" => "implicit",
484 "password" => "password",
485 "application" => "clientCredentials",
486 "accessCode" => "authorizationCode",
487 _ => "implicit",
488 };
489
490 if let Some(auth_url) = def.get("authorizationUrl") {
491 flow_obj.insert("authorizationUrl".to_string(), auth_url.clone());
492 }
493 if let Some(token_url) = def.get("tokenUrl") {
494 flow_obj.insert("tokenUrl".to_string(), token_url.clone());
495 }
496 if let Some(scopes) = def.get("scopes") {
497 flow_obj.insert("scopes".to_string(), scopes.clone());
498 } else {
499 flow_obj.insert("scopes".to_string(), json!({}));
500 }
501
502 flows.insert(flow_name.to_string(), Value::Object(flow_obj));
503 converted.insert("flows".to_string(), Value::Object(flows));
504 }
505 _ => {
506 for (key, value) in def {
508 converted.insert(key.clone(), value.clone());
509 }
510 }
511 }
512
513 if let Some(desc) = def.get("description") {
515 converted.insert("description".to_string(), desc.clone());
516 }
517
518 converted
519}
520
521fn convert_ref(ref_str: &str) -> String {
523 if let Some(name) = ref_str.strip_prefix("#/definitions/") {
525 format!("#/components/schemas/{}", name)
526 } else {
527 ref_str.to_string()
528 }
529}
530
531fn convert_schema_refs(schema: &Value) -> Value {
533 match schema {
534 Value::Object(obj) => {
535 let mut converted = Map::new();
536 for (key, value) in obj {
537 if key == "$ref" {
538 if let Some(ref_str) = value.as_str() {
539 converted.insert(key.clone(), json!(convert_ref(ref_str)));
540 } else {
541 converted.insert(key.clone(), value.clone());
542 }
543 } else {
544 converted.insert(key.clone(), convert_schema_refs(value));
545 }
546 }
547 Value::Object(converted)
548 }
549 Value::Array(arr) => Value::Array(arr.iter().map(convert_schema_refs).collect()),
550 _ => schema.clone(),
551 }
552}
553
554pub fn is_swagger_2(value: &Value) -> bool {
556 value.get("swagger").and_then(|v| v.as_str()) == Some("2.0")
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 #[test]
564 fn test_is_swagger_2() {
565 assert!(is_swagger_2(&json!({"swagger": "2.0"})));
566 assert!(!is_swagger_2(&json!({"openapi": "3.0.0"})));
567 }
568
569 #[test]
570 fn test_convert_servers() {
571 let swagger = json!({
572 "swagger": "2.0",
573 "host": "api.example.com",
574 "basePath": "/v1",
575 "schemes": ["https", "http"]
576 });
577
578 let servers = convert_servers(&swagger);
579 assert_eq!(servers.len(), 2);
580 assert_eq!(servers[0]["url"], "https://api.example.com/v1");
581 assert_eq!(servers[1]["url"], "http://api.example.com/v1");
582 }
583
584 #[test]
585 fn test_convert_ref() {
586 assert_eq!(convert_ref("#/definitions/User"), "#/components/schemas/User");
587 assert_eq!(convert_ref("#/components/schemas/User"), "#/components/schemas/User");
588 }
589
590 #[test]
591 fn test_convert_parameter() {
592 let param = json!({
593 "name": "userId",
594 "in": "path",
595 "required": true,
596 "type": "string",
597 "format": "uuid"
598 });
599
600 let converted = convert_parameter(¶m);
601 assert_eq!(converted["name"], "userId");
602 assert_eq!(converted["in"], "path");
603 assert_eq!(converted["schema"]["type"], "string");
604 assert_eq!(converted["schema"]["format"], "uuid");
605 }
606
607 #[test]
608 fn test_convert_security_basic() {
609 let def = json!({
610 "type": "basic",
611 "description": "Basic auth"
612 });
613
614 if let Some(def_obj) = def.as_object() {
615 let converted = convert_security_definition(def_obj);
616 assert_eq!(converted["type"], json!("http"));
617 assert_eq!(converted["scheme"], json!("basic"));
618 }
619 }
620
621 #[test]
622 fn test_basic_conversion() {
623 let swagger = json!({
624 "swagger": "2.0",
625 "info": {
626 "title": "Test API",
627 "version": "1.0.0"
628 },
629 "host": "api.example.com",
630 "basePath": "/v1",
631 "schemes": ["https"],
632 "paths": {
633 "/users": {
634 "get": {
635 "operationId": "getUsers",
636 "produces": ["application/json"],
637 "responses": {
638 "200": {
639 "description": "Success"
640 }
641 }
642 }
643 }
644 }
645 });
646
647 let result = convert_swagger_to_openapi3(&swagger).unwrap();
648 assert_eq!(result["openapi"], "3.0.3");
649 assert_eq!(result["info"]["title"], "Test API");
650 assert!(result["servers"].as_array().is_some());
651 assert!(result["paths"]["/users"]["get"].is_object());
652 }
653}