1use crate::import::schema_data_generator::generate_from_schema;
7use mockforge_openapi::OpenApiSpec;
8
9use once_cell::sync::Lazy;
10use regex::Regex;
11use serde::Serialize;
12use serde_json::{json, Value};
13use std::collections::HashMap;
14
15static PATH_PARAM_RE: Lazy<Regex> =
17 Lazy::new(|| Regex::new(r"\{([^}]+)\}").expect("PATH_PARAM_RE regex is valid"));
18
19#[derive(Debug)]
21pub struct OpenApiImportResult {
22 pub routes: Vec<MockForgeRoute>,
24 pub warnings: Vec<String>,
26 pub spec_info: OpenApiSpecInfo,
28}
29
30#[derive(Debug, Serialize)]
32pub struct MockForgeRoute {
33 pub method: String,
35 pub path: String,
37 pub headers: HashMap<String, String>,
39 pub body: Option<String>,
41 pub response: MockForgeResponse,
43}
44
45#[derive(Debug, Serialize)]
47pub struct MockForgeResponse {
48 pub status: u16,
50 pub headers: HashMap<String, String>,
52 pub body: Value,
54}
55
56#[derive(Debug)]
58pub struct OpenApiSpecInfo {
59 pub title: String,
61 pub version: String,
63 pub description: Option<String>,
65 pub openapi_version: String,
67 pub servers: Vec<String>,
69}
70
71pub fn import_openapi_spec(
73 content: &str,
74 _base_url: Option<&str>,
75) -> Result<OpenApiImportResult, String> {
76 let format = mockforge_openapi::spec_parser::SpecFormat::detect(content, None)
78 .map_err(|e| format!("Failed to detect spec format: {}", e))?;
79
80 let mut json_value: Value = match serde_json::from_str::<Value>(content) {
83 Ok(val) => val,
84 Err(_) => {
85 serde_yaml::from_str(content)
87 .map_err(|e| format!("Failed to parse as JSON or YAML: {}", e))?
88 }
89 };
90
91 match format {
93 mockforge_openapi::spec_parser::SpecFormat::OpenApi20 => {
94 let validation =
95 mockforge_openapi::spec_parser::OpenApiValidator::validate(&json_value, format);
96 if !validation.is_valid {
97 let error_msg = validation
99 .errors
100 .iter()
101 .map(|e| format!(" - {}", e))
102 .collect::<Vec<_>>()
103 .join("\n");
104 return Err(format!("Invalid OpenAPI 2.0 (Swagger) specification:\n{}", error_msg));
105 }
106
107 json_value = super::swagger2_convert::convert_swagger2_to_openapi3(&json_value)?;
111 }
112 mockforge_openapi::spec_parser::SpecFormat::OpenApi30
113 | mockforge_openapi::spec_parser::SpecFormat::OpenApi31 => {
114 let validation =
115 mockforge_openapi::spec_parser::OpenApiValidator::validate(&json_value, format);
116 if !validation.is_valid {
117 let error_msg = validation
119 .errors
120 .iter()
121 .map(|e| format!(" - {}", e))
122 .collect::<Vec<_>>()
123 .join("\n");
124 return Err(format!("Invalid OpenAPI specification:\n{}", error_msg));
125 }
126 }
128 _ => {
129 return Err(format!(
130 "Unsupported specification format: {}. Only OpenAPI 3.x is currently supported for parsing.",
131 format.display_name()
132 ));
133 }
134 }
135
136 let spec = OpenApiSpec::from_json(json_value)
137 .map_err(|e| format!("Failed to load OpenAPI spec: {}", e))?;
138
139 spec.validate().map_err(|e| format!("Invalid OpenAPI specification: {}", e))?;
140
141 let spec_info = OpenApiSpecInfo {
143 title: spec.title().to_string(),
144 version: spec.api_version().to_string(),
145 description: spec.description().map(|s| s.to_string()),
146 openapi_version: spec.version().to_string(),
147 servers: spec
148 .spec
149 .servers
150 .iter()
151 .filter_map(|server| server.url.parse::<url::Url>().ok())
152 .map(|url| url.to_string())
153 .collect(),
154 };
155
156 let mut routes = Vec::new();
157 let mut warnings = Vec::new();
158
159 let path_operations = spec.all_paths_and_operations();
161
162 let mut sorted_paths: Vec<_> = path_operations.iter().collect();
164 sorted_paths.sort_by_key(|(path, _)| path.as_str());
165
166 for (path, operations) in sorted_paths {
167 let method_order = [
169 "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE",
170 ];
171
172 for method in method_order {
173 if let Some(operation) = operations.get(method) {
174 match convert_operation_to_route(&spec, method, path, operation, _base_url) {
175 Ok(route) => routes.push(route),
176 Err(e) => warnings.push(format!("Failed to convert {method} {path}: {e}")),
177 }
178 }
179 }
180 }
181
182 Ok(OpenApiImportResult {
183 routes,
184 warnings,
185 spec_info,
186 })
187}
188
189fn convert_operation_to_route(
191 spec: &OpenApiSpec,
192 method: &str,
193 path: &str,
194 operation: &openapiv3::Operation,
195 _base_url: Option<&str>,
196) -> Result<MockForgeRoute, String> {
197 let mut response_status = 200;
199 let mut response_body = Value::Object(serde_json::Map::new());
200 let mut response_headers = HashMap::new();
201
202 for (status_code, response_ref) in &operation.responses.responses {
204 let is_success = match status_code {
206 openapiv3::StatusCode::Code(code) => (200..300).contains(code),
207 openapiv3::StatusCode::Range(range) => *range == 2, };
209
210 if is_success {
211 let status = match status_code {
212 openapiv3::StatusCode::Code(code) => *code,
213 openapiv3::StatusCode::Range(_) => 200, };
215
216 if (200..300).contains(&status) {
217 response_status = status;
218
219 if let Some(response) = response_ref.as_item() {
221 response_headers
223 .insert("Content-Type".to_string(), "application/json".to_string());
224
225 if let Some(content) = response.content.get("application/json") {
227 if let Some(example) = &content.example {
229 response_body = example.clone();
230 } else if !content.examples.is_empty() {
231 if let Some((_key, example_ref)) = content.examples.iter().next() {
233 if let Some(example_value) = example_ref.as_item() {
234 if let Some(value) = &example_value.value {
235 response_body = value.clone();
236 }
237 }
238 }
239 } else if let Some(schema_ref) = &content.schema {
240 response_body = if let Some(resolved) =
242 resolve_schema_ref(schema_ref, &spec.spec)
243 {
244 generate_response_from_openapi_schema(&resolved)
245 } else {
246 serde_json::json!({"message": "Mock response", "path": path, "method": method})
247 };
248 } else {
249 response_body = serde_json::json!({"message": "Success"});
251 }
252 } else {
253 response_body = serde_json::json!({"message": "Success"});
255 }
256 } else {
257 response_body = serde_json::json!({"message": "Mock response"});
259 }
260 break;
261 }
262 }
263 }
264
265 if response_status == 200 && operation.responses.default.is_some() {
267 response_body = serde_json::json!({"message": "Default response"});
268 }
269
270 let mock_response = MockForgeResponse {
271 status: response_status,
272 headers: response_headers,
273 body: response_body,
274 };
275
276 let converted_path = convert_path_parameters(path);
278
279 let request_body = if let Some(request_body_ref) = &operation.request_body {
281 extract_request_body_example(request_body_ref, &spec.spec)
282 } else {
283 None
284 };
285
286 Ok(MockForgeRoute {
287 method: method.to_uppercase(),
288 path: converted_path,
289 headers: HashMap::new(), body: request_body,
291 response: mock_response,
292 })
293}
294
295fn extract_request_body_example(
297 request_body_ref: &openapiv3::ReferenceOr<openapiv3::RequestBody>,
298 spec: &openapiv3::OpenAPI,
299) -> Option<String> {
300 let request_body = match request_body_ref {
301 openapiv3::ReferenceOr::Item(rb) => rb.clone(),
302 openapiv3::ReferenceOr::Reference { reference } => {
303 let name = reference.strip_prefix("#/components/requestBodies/")?;
305 let components = spec.components.as_ref()?;
306 let rb_ref = components.request_bodies.get(name)?;
307 match rb_ref {
308 openapiv3::ReferenceOr::Item(rb) => rb.clone(),
309 openapiv3::ReferenceOr::Reference { .. } => return None,
310 }
311 }
312 };
313
314 let media_type = request_body.content.get("application/json")?;
316
317 if let Some(example) = &media_type.example {
319 if let Ok(example_str) = serde_json::to_string(example) {
320 return Some(example_str);
321 }
322 }
323
324 if let Some(schema_ref) = &media_type.schema {
326 let schema = resolve_schema_ref(schema_ref, spec);
327 if let Some(s) = schema {
328 let json_schema = openapi_schema_to_json_schema(&s);
329 let generated = generate_from_schema(&json_schema);
330 if let Ok(s) = serde_json::to_string(&generated) {
331 return Some(s);
332 }
333 }
334 }
335
336 None
337}
338
339fn resolve_schema_ref(
341 schema_ref: &openapiv3::ReferenceOr<openapiv3::Schema>,
342 spec: &openapiv3::OpenAPI,
343) -> Option<openapiv3::Schema> {
344 match schema_ref {
345 openapiv3::ReferenceOr::Item(schema) => Some(schema.clone()),
346 openapiv3::ReferenceOr::Reference { reference } => {
347 let name = reference.strip_prefix("#/components/schemas/")?;
348 let components = spec.components.as_ref()?;
349 let resolved = components.schemas.get(name)?;
350 match resolved {
351 openapiv3::ReferenceOr::Item(schema) => Some(schema.clone()),
352 openapiv3::ReferenceOr::Reference { .. } => None,
353 }
354 }
355 }
356}
357
358fn convert_path_parameters(path: &str) -> String {
360 PATH_PARAM_RE.replace_all(path, ":$1").to_string()
361}
362
363fn generate_response_from_openapi_schema(schema: &openapiv3::Schema) -> Value {
365 let json_schema = openapi_schema_to_json_schema(schema);
367 generate_from_schema(&json_schema)
368}
369
370fn openapi_schema_to_json_schema(schema: &openapiv3::Schema) -> Value {
372 match &schema.schema_kind {
373 openapiv3::SchemaKind::Type(type_schema) => match type_schema {
374 openapiv3::Type::String(string_type) => {
375 let mut obj = serde_json::Map::new();
376 obj.insert("type".to_string(), json!("string"));
377
378 if !matches!(string_type.format, openapiv3::VariantOrUnknownOrEmpty::Empty) {
380 obj.insert("format".to_string(), json!(format!("{:?}", string_type.format)));
381 }
382
383 if !string_type.enumeration.is_empty() {
385 let enum_values: Vec<Value> = string_type
386 .enumeration
387 .iter()
388 .filter_map(|s| s.as_ref().map(|s| json!(s)))
389 .collect();
390 if !enum_values.is_empty() {
391 obj.insert("enum".to_string(), json!(enum_values));
392 }
393 }
394
395 Value::Object(obj)
396 }
397 openapiv3::Type::Number(_) => {
398 json!({"type": "number"})
399 }
400 openapiv3::Type::Integer(_) => {
401 json!({"type": "integer"})
402 }
403 openapiv3::Type::Boolean(_) => {
404 json!({"type": "boolean"})
405 }
406 openapiv3::Type::Array(array_type) => {
407 let mut obj = serde_json::Map::new();
408 obj.insert("type".to_string(), json!("array"));
409
410 if let Some(items) = &array_type.items {
411 if let Some(item_schema) = items.as_item() {
412 obj.insert("items".to_string(), openapi_schema_to_json_schema(item_schema));
413 }
414 }
415
416 Value::Object(obj)
417 }
418 openapiv3::Type::Object(object_type) => {
419 let mut obj = serde_json::Map::new();
420 obj.insert("type".to_string(), json!("object"));
421
422 if !object_type.properties.is_empty() {
423 let mut props = serde_json::Map::new();
424 for (name, schema_ref) in &object_type.properties {
425 if let Some(prop_schema) = schema_ref.as_item() {
426 props.insert(name.clone(), openapi_schema_to_json_schema(prop_schema));
427 }
428 }
429 obj.insert("properties".to_string(), Value::Object(props));
430 }
431
432 if !object_type.required.is_empty() {
433 obj.insert("required".to_string(), json!(object_type.required));
434 }
435
436 Value::Object(obj)
437 }
438 },
439 openapiv3::SchemaKind::OneOf { one_of } => {
440 if let Some(first) = one_of.first() {
442 if let Some(schema) = first.as_item() {
443 return openapi_schema_to_json_schema(schema);
444 }
445 }
446 json!({"type": "object"})
447 }
448 openapiv3::SchemaKind::AllOf { all_of } => {
449 let mut properties = serde_json::Map::new();
451 let mut required = Vec::new();
452 for schema_ref in all_of {
453 if let Some(sub_schema) = schema_ref.as_item() {
454 let converted = openapi_schema_to_json_schema(sub_schema);
455 if let Some(obj) = converted.as_object() {
456 if let Some(props) = obj.get("properties").and_then(|p| p.as_object()) {
457 for (k, v) in props {
458 properties.insert(k.clone(), v.clone());
459 }
460 }
461 if let Some(req) = obj.get("required").and_then(|r| r.as_array()) {
462 for r in req {
463 if let Some(s) = r.as_str() {
464 required.push(json!(s));
465 }
466 }
467 }
468 }
469 }
470 }
471 let mut result = serde_json::Map::new();
472 result.insert("type".to_string(), json!("object"));
473 if !properties.is_empty() {
474 result.insert("properties".to_string(), Value::Object(properties));
475 }
476 if !required.is_empty() {
477 result.insert("required".to_string(), Value::Array(required));
478 }
479 Value::Object(result)
480 }
481 openapiv3::SchemaKind::AnyOf { any_of } => {
482 if let Some(first) = any_of.first() {
484 if let Some(schema) = first.as_item() {
485 return openapi_schema_to_json_schema(schema);
486 }
487 }
488 json!({"type": "object"})
489 }
490 openapiv3::SchemaKind::Not { .. } => {
491 json!({"type": "object"})
492 }
493 openapiv3::SchemaKind::Any(_) => {
494 json!({"type": "object"})
495 }
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn test_import_openapi_spec() {
505 let openapi_json = r#"{
506 "openapi": "3.0.3",
507 "info": {
508 "title": "Test API",
509 "version": "1.0.0",
510 "description": "A test API"
511 },
512 "paths": {
513 "/users": {
514 "get": {
515 "operationId": "getUsers",
516 "summary": "Get all users",
517 "responses": {
518 "200": {
519 "description": "Successful response",
520 "content": {
521 "application/json": {
522 "schema": {
523 "type": "array",
524 "items": {
525 "type": "object",
526 "properties": {
527 "id": {"type": "integer"},
528 "name": {"type": "string"}
529 }
530 }
531 }
532 }
533 }
534 }
535 }
536 }
537 }
538 }
539 }"#;
540
541 let result = import_openapi_spec(openapi_json, Some("/api")).unwrap();
542
543 assert_eq!(result.routes.len(), 1);
544 assert_eq!(result.routes[0].method, "GET");
545 assert_eq!(result.routes[0].path, "/users");
546 assert_eq!(result.routes[0].response.status, 200);
547
548 assert_eq!(result.spec_info.title, "Test API");
550 assert_eq!(result.spec_info.version, "1.0.0");
551 }
552
553 #[test]
554 fn test_import_openapi_with_parameters() {
555 let openapi_json = r#"{
556 "openapi": "3.0.3",
557 "info": {
558 "title": "Test API",
559 "version": "1.0.0"
560 },
561 "paths": {
562 "/users/{userId}": {
563 "get": {
564 "operationId": "getUser",
565 "parameters": [
566 {
567 "name": "userId",
568 "in": "path",
569 "required": true,
570 "schema": {"type": "string"}
571 }
572 ],
573 "responses": {
574 "200": {
575 "description": "User info",
576 "content": {
577 "application/json": {
578 "schema": {
579 "type": "object",
580 "properties": {
581 "id": {"type": "string"},
582 "name": {"type": "string"}
583 }
584 }
585 }
586 }
587 }
588 }
589 }
590 }
591 }
592 }"#;
593
594 let result = import_openapi_spec(openapi_json, None).unwrap();
595
596 assert_eq!(result.routes.len(), 1);
597 assert_eq!(result.routes[0].path, "/users/:userId");
598 }
599
600 #[test]
601 fn test_import_openapi_with_multiple_operations() {
602 let openapi_json = r#"{
603 "openapi": "3.0.3",
604 "info": {
605 "title": "User API",
606 "version": "1.0.0"
607 },
608 "paths": {
609 "/users": {
610 "get": {
611 "operationId": "listUsers",
612 "responses": {
613 "200": {
614 "description": "List of users",
615 "content": {
616 "application/json": {
617 "schema": {
618 "type": "array",
619 "items": {"type": "object"}
620 }
621 }
622 }
623 }
624 }
625 },
626 "post": {
627 "operationId": "createUser",
628 "requestBody": {
629 "required": true,
630 "content": {
631 "application/json": {
632 "schema": {
633 "type": "object",
634 "properties": {
635 "name": {"type": "string"},
636 "email": {"type": "string"}
637 }
638 }
639 }
640 }
641 },
642 "responses": {
643 "201": {
644 "description": "User created",
645 "content": {
646 "application/json": {
647 "schema": {"type": "object"}
648 }
649 }
650 }
651 }
652 }
653 },
654 "/users/{id}": {
655 "get": {
656 "operationId": "getUser",
657 "parameters": [
658 {"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}
659 ],
660 "responses": {
661 "200": {
662 "description": "User details",
663 "content": {
664 "application/json": {
665 "schema": {"type": "object"}
666 }
667 }
668 }
669 }
670 },
671 "put": {
672 "operationId": "updateUser",
673 "parameters": [
674 {"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}
675 ],
676 "requestBody": {
677 "required": true,
678 "content": {
679 "application/json": {
680 "schema": {"type": "object"}
681 }
682 }
683 },
684 "responses": {
685 "200": {
686 "description": "User updated",
687 "content": {
688 "application/json": {
689 "schema": {"type": "object"}
690 }
691 }
692 }
693 }
694 },
695 "delete": {
696 "operationId": "deleteUser",
697 "parameters": [
698 {"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}
699 ],
700 "responses": {
701 "204": {
702 "description": "User deleted"
703 }
704 }
705 }
706 }
707 }
708 }"#;
709
710 let result = import_openapi_spec(openapi_json, None).unwrap();
711
712 assert_eq!(result.routes.len(), 5);
713
714 assert_eq!(result.routes[0].method, "GET");
716 assert_eq!(result.routes[0].path, "/users");
717 assert_eq!(result.routes[0].response.status, 200);
718
719 assert_eq!(result.routes[1].method, "POST");
720 assert_eq!(result.routes[1].path, "/users");
721 assert_eq!(result.routes[1].response.status, 201);
722
723 assert_eq!(result.routes[2].method, "GET");
724 assert_eq!(result.routes[2].path, "/users/:id");
725
726 assert_eq!(result.routes[3].method, "PUT");
727 assert_eq!(result.routes[3].path, "/users/:id");
728 assert_eq!(result.routes[3].response.status, 200);
729
730 assert_eq!(result.routes[4].method, "DELETE");
731 assert_eq!(result.routes[4].path, "/users/:id");
732 assert_eq!(result.routes[4].response.status, 204);
733 }
734
735 #[test]
736 fn test_import_openapi_with_query_parameters() {
737 let openapi_json = r#"{
738 "openapi": "3.0.3",
739 "info": {
740 "title": "Search API",
741 "version": "1.0.0"
742 },
743 "paths": {
744 "/search": {
745 "get": {
746 "operationId": "searchUsers",
747 "parameters": [
748 {"name": "query", "in": "query", "required": true, "schema": {"type": "string"}},
749 {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 10}},
750 {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0}}
751 ],
752 "responses": {
753 "200": {
754 "description": "Search results",
755 "content": {
756 "application/json": {
757 "schema": {"type": "object"}
758 }
759 }
760 }
761 }
762 }
763 }
764 }
765 }"#;
766
767 let result = import_openapi_spec(openapi_json, None).unwrap();
768
769 assert_eq!(result.routes.len(), 1);
770 assert_eq!(result.routes[0].method, "GET");
771 assert_eq!(result.routes[0].path, "/search");
772 }
773
774 #[test]
775 fn test_import_openapi_with_request_body() {
776 let openapi_json = r#"{
777 "openapi": "3.0.3",
778 "info": {
779 "title": "User API",
780 "version": "1.0.0"
781 },
782 "paths": {
783 "/users": {
784 "post": {
785 "operationId": "createUser",
786 "requestBody": {
787 "required": true,
788 "content": {
789 "application/json": {
790 "schema": {
791 "type": "object",
792 "properties": {
793 "name": {"type": "string"},
794 "email": {"type": "string"},
795 "age": {"type": "integer"}
796 },
797 "required": ["name", "email"]
798 },
799 "example": {
800 "name": "John Doe",
801 "email": "john@example.com",
802 "age": 30
803 }
804 }
805 }
806 },
807 "responses": {
808 "201": {
809 "description": "User created",
810 "content": {
811 "application/json": {
812 "schema": {"type": "object"}
813 }
814 }
815 }
816 }
817 }
818 }
819 }
820 }"#;
821
822 let result = import_openapi_spec(openapi_json, None).unwrap();
823
824 assert_eq!(result.routes.len(), 1);
825 assert_eq!(result.routes[0].method, "POST");
826 assert_eq!(result.routes[0].path, "/users");
827 assert_eq!(result.routes[0].response.status, 201);
828 assert!(result.routes[0].body.is_some());
829 }
830
831 #[test]
832 fn test_import_openapi_with_different_response_codes() {
833 let openapi_json = r#"{
834 "openapi": "3.0.3",
835 "info": {
836 "title": "Test API",
837 "version": "1.0.0"
838 },
839 "paths": {
840 "/users": {
841 "get": {
842 "responses": {
843 "200": {"description": "Success"},
844 "400": {"description": "Bad Request"},
845 "404": {"description": "Not Found"},
846 "500": {"description": "Internal Error"}
847 }
848 }
849 }
850 }
851 }"#;
852
853 let result = import_openapi_spec(openapi_json, None).unwrap();
854
855 assert_eq!(result.routes.len(), 1);
856 assert_eq!(result.routes[0].method, "GET");
857 assert_eq!(result.routes[0].path, "/users");
858 assert_eq!(result.routes[0].response.status, 200);
860 }
861
862 #[test]
863 fn test_import_openapi_with_default_response() {
864 let openapi_json = r#"{
865 "openapi": "3.0.3",
866 "info": {
867 "title": "Test API",
868 "version": "1.0.0"
869 },
870 "paths": {
871 "/users": {
872 "get": {
873 "responses": {
874 "default": {
875 "description": "Default response",
876 "content": {
877 "application/json": {
878 "schema": {"type": "object"}
879 }
880 }
881 }
882 }
883 }
884 }
885 }
886 }"#;
887
888 let result = import_openapi_spec(openapi_json, None).unwrap();
889
890 assert_eq!(result.routes.len(), 1);
891 assert_eq!(result.routes[0].method, "GET");
892 assert_eq!(result.routes[0].path, "/users");
893 assert_eq!(result.routes[0].response.status, 200); }
895
896 #[test]
897 fn test_import_openapi_with_schema_references() {
898 let openapi_json = r##"{
899 "openapi": "3.0.3",
900 "info": {
901 "title": "Test API",
902 "version": "1.0.0"
903 },
904 "components": {
905 "schemas": {
906 "User": {
907 "type": "object",
908 "properties": {
909 "id": {"type": "integer"},
910 "name": {"type": "string"},
911 "email": {"type": "string"}
912 }
913 },
914 "Error": {
915 "type": "object",
916 "properties": {
917 "code": {"type": "integer"},
918 "message": {"type": "string"}
919 }
920 }
921 }
922 },
923 "paths": {
924 "/users": {
925 "get": {
926 "responses": {
927 "200": {
928 "description": "Success",
929 "content": {
930 "application/json": {
931 "schema": {"$ref": "#components/schemas/User"}
932 }
933 }
934 }
935 }
936 }
937 }
938 }
939 }"##;
940
941 let result = import_openapi_spec(openapi_json, None).unwrap();
942
943 assert_eq!(result.routes.len(), 1);
944 assert_eq!(result.routes[0].method, "GET");
945 assert_eq!(result.routes[0].path, "/users");
946 assert_eq!(result.routes[0].response.status, 200);
947 }
948
949 #[test]
950 fn test_import_openapi_with_array_responses() {
951 let openapi_json = r#"{
952 "openapi": "3.0.3",
953 "info": {
954 "title": "Test API",
955 "version": "1.0.0"
956 },
957 "paths": {
958 "/users": {
959 "get": {
960 "responses": {
961 "200": {
962 "description": "List of users",
963 "content": {
964 "application/json": {
965 "schema": {
966 "type": "array",
967 "items": {
968 "type": "object",
969 "properties": {
970 "id": {"type": "integer"},
971 "name": {"type": "string"}
972 }
973 }
974 }
975 }
976 }
977 }
978 }
979 }
980 }
981 }
982 }"#;
983
984 let result = import_openapi_spec(openapi_json, None).unwrap();
985
986 assert_eq!(result.routes.len(), 1);
987 assert_eq!(result.routes[0].method, "GET");
988 assert_eq!(result.routes[0].path, "/users");
989 assert_eq!(result.routes[0].response.status, 200);
990 }
991
992 #[test]
993 fn test_import_openapi_with_complex_schema() {
994 let openapi_json = r#"{
995 "openapi": "3.0.3",
996 "info": {
997 "title": "Complex API",
998 "version": "1.0.0"
999 },
1000 "paths": {
1001 "/users/{userId}/posts": {
1002 "get": {
1003 "parameters": [
1004 {"name": "userId", "in": "path", "required": true, "schema": {"type": "string"}},
1005 {"name": "includeComments", "in": "query", "required": false, "schema": {"type": "boolean"}},
1006 {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 10}}
1007 ],
1008 "responses": {
1009 "200": {
1010 "description": "User posts",
1011 "content": {
1012 "application/json": {
1013 "schema": {
1014 "type": "object",
1015 "properties": {
1016 "posts": {
1017 "type": "array",
1018 "items": {
1019 "type": "object",
1020 "properties": {
1021 "id": {"type": "integer"},
1022 "title": {"type": "string"},
1023 "content": {"type": "string"},
1024 "author": {
1025 "type": "object",
1026 "properties": {
1027 "id": {"type": "integer"},
1028 "name": {"type": "string"}
1029 }
1030 },
1031 "tags": {
1032 "type": "array",
1033 "items": {"type": "string"}
1034 }
1035 }
1036 }
1037 },
1038 "total": {"type": "integer"},
1039 "page": {"type": "integer"}
1040 }
1041 }
1042 }
1043 }
1044 }
1045 }
1046 }
1047 }
1048 }
1049 }"#;
1050
1051 let result = import_openapi_spec(openapi_json, None).unwrap();
1052
1053 assert_eq!(result.routes.len(), 1);
1054 assert_eq!(result.routes[0].method, "GET");
1055 assert_eq!(result.routes[0].path, "/users/:userId/posts");
1056 assert_eq!(result.routes[0].response.status, 200);
1057 }
1058
1059 #[test]
1060 fn test_import_openapi_with_base_url() {
1061 let openapi_json = r#"{
1062 "openapi": "3.0.3",
1063 "info": {
1064 "title": "Test API",
1065 "version": "1.0.0"
1066 },
1067 "servers": [
1068 {"url": "https://api.example.com/v1"},
1069 {"url": "https://dev.example.com/v1"}
1070 ],
1071 "paths": {
1072 "/users": {
1073 "get": {
1074 "responses": {
1075 "200": {
1076 "description": "Success",
1077 "content": {
1078 "application/json": {
1079 "schema": {"type": "object"}
1080 }
1081 }
1082 }
1083 }
1084 }
1085 }
1086 }
1087 }"#;
1088
1089 let result = import_openapi_spec(openapi_json, Some("https://api.example.com/v1")).unwrap();
1090
1091 assert_eq!(result.routes.len(), 1);
1092 assert_eq!(result.routes[0].method, "GET");
1093 assert_eq!(result.routes[0].path, "/users");
1094
1095 assert_eq!(result.spec_info.servers.len(), 2);
1097 assert!(result.spec_info.servers.contains(&"https://api.example.com/v1".to_string()));
1098 assert!(result.spec_info.servers.contains(&"https://dev.example.com/v1".to_string()));
1099 }
1100
1101 #[test]
1102 fn test_import_openapi_with_invalid_json() {
1103 let invalid_openapi_json = r#"{
1104 "openapi": "3.0.3",
1105 "info": {
1106 "title": "Test API",
1107 "version": "1.0.0"
1108 },
1109 "paths": {
1110 "/users": {
1111 "get": {
1112 "responses": {
1113 "200": {
1114 "description": "Success"
1115 }
1116 }
1117 }
1118 }
1119 }
1120 }"#;
1121
1122 let result = import_openapi_spec(invalid_openapi_json, None);
1123 assert!(result.is_ok());
1125 assert_eq!(result.unwrap().routes.len(), 1);
1126 }
1127
1128 #[test]
1129 fn test_import_openapi_with_no_responses() {
1130 let openapi_json = r#"{
1131 "openapi": "3.0.3",
1132 "info": {
1133 "title": "Test API",
1134 "version": "1.0.0"
1135 },
1136 "paths": {
1137 "/users": {
1138 "get": {
1139 "operationId": "getUsers",
1140 "responses": {}
1141 }
1142 }
1143 }
1144 }"#;
1145
1146 let result = import_openapi_spec(openapi_json, None);
1147 assert!(result.is_ok());
1149 let routes = result.unwrap().routes;
1150 assert_eq!(routes.len(), 1);
1151 assert_eq!(routes[0].response.status, 200); }
1153}