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