1use super::HttpBridgeConfig;
7use regex::Regex;
8
9#[derive(Debug, Clone)]
11pub struct RouteGenerator {
12 config: HttpBridgeConfig,
14 service_name_regex: Regex,
16 method_name_regex: Regex,
18}
19
20impl RouteGenerator {
21 pub fn new(config: HttpBridgeConfig) -> Self {
23 Self {
24 config,
25 service_name_regex: Regex::new(r"[^a-zA-Z0-9_.-]").unwrap(),
26 method_name_regex: Regex::new(r"[^a-zA-Z0-9_.-]").unwrap(),
27 }
28 }
29
30 pub fn generate_route_path(&self, service_name: &str, method_name: &str) -> String {
32 let clean_service = self.clean_service_name(service_name);
33 let clean_method = self.clean_method_name(method_name);
34
35 format!("{}{}", self.config.base_path, self.config.route_pattern)
36 .replace("{service}", &clean_service)
37 .replace("{method}", &clean_method)
38 }
39
40 pub fn generate_url_pattern(&self, _service_name: &str, _method_name: &str) -> String {
42 let template = format!("{}{}", self.config.base_path, self.config.route_pattern);
43 template.replace("{service}", "[^/]+").replace("{method}", "[^/]+")
45 }
46
47 pub fn clean_service_name(&self, service_name: &str) -> String {
49 let without_package = if let Some(dot_index) = service_name.rfind('.') {
51 &service_name[dot_index + 1..]
52 } else {
53 service_name
54 };
55
56 let cleaned = self.service_name_regex.replace_all(without_package, "-");
58
59 cleaned.to_lowercase()
61 }
62
63 pub fn clean_method_name(&self, method_name: &str) -> String {
65 let cleaned = self.method_name_regex.replace_all(method_name, "-");
67
68 cleaned.to_lowercase()
70 }
71
72 pub fn generate_openapi_path(
74 &self,
75 service_name: &str,
76 method_name: &str,
77 method_descriptor: &prost_reflect::MethodDescriptor,
78 ) -> serde_json::Value {
79 let path = self.generate_route_path(service_name, method_name);
80 let operation =
81 self.generate_openapi_operation(service_name, method_name, method_descriptor);
82
83 serde_json::json!({ path: operation })
84 }
85
86 pub fn generate_openapi_spec(
88 &self,
89 services: &std::collections::HashMap<String, crate::dynamic::proto_parser::ProtoService>,
90 ) -> serde_json::Value {
91 let mut paths = serde_json::Map::new();
92 let mut schemas = serde_json::Map::new();
93
94 for (service_name, service) in services {
96 for method in &service.methods {
97 let path = self.generate_route_path(service_name, &method.name);
98 let operation = self.generate_openapi_operation_full(
99 service_name,
100 &method.name,
101 method,
102 &mut schemas,
103 );
104
105 let http_method = if method.server_streaming {
107 "get"
108 } else {
109 "post"
110 };
111
112 paths.insert(
113 path,
114 serde_json::json!({
115 http_method: operation
116 }),
117 );
118 }
119 }
120
121 serde_json::json!({
122 "openapi": "3.0.1",
123 "info": {
124 "title": "MockForge gRPC HTTP Bridge API",
125 "description": "RESTful API bridge to gRPC services automatically generated from protobuf definitions",
126 "version": "1.0.0",
127 "contact": {
128 "name": "MockForge",
129 "url": "https://github.com/SaaSy-Solutions/mockforge"
130 },
131 "license": {
132 "name": "MIT",
133 "url": "https://opensource.org/licenses/MIT"
134 }
135 },
136 "servers": [{
137 "url": "http://localhost:9080",
138 "description": "Local development server"
139 }],
140 "security": [],
141 "paths": paths,
142 "components": {
143 "schemas": schemas,
144 "securitySchemes": {}
145 }
146 })
147 }
148
149 fn generate_openapi_operation_full(
151 &self,
152 service_name: &str,
153 method_name: &str,
154 proto_method: &crate::dynamic::proto_parser::ProtoMethod,
155 schemas: &mut serde_json::Map<String, serde_json::Value>,
156 ) -> serde_json::Value {
157 let mut operation = serde_json::json!({
158 "summary": format!("{} {}", method_name, service_name),
159 "description": format!("Calls the {} method on {} service\n\n**gRPC Method Details:**\n- Service: {}\n- Method: {}\n- Input: {}\n- Output: {}\n{}",
160 method_name,
161 service_name,
162 service_name,
163 method_name,
164 proto_method.input_type,
165 proto_method.output_type,
166 if proto_method.server_streaming {
167 "\n- Server Streaming: Yes (returns SSE stream)"
168 } else {
169 "\n- Server Streaming: No"
170 }
171 ),
172 "tags": [service_name],
173 "parameters": self.generate_openapi_parameters(),
174 });
175
176 if !proto_method.server_streaming {
178 operation["requestBody"] =
179 self.generate_openapi_request_body_full(proto_method, schemas);
180 }
181
182 operation["responses"] =
184 self.generate_openapi_responses_full(service_name, method_name, proto_method, schemas);
185
186 operation
187 }
188
189 fn generate_openapi_request_body_full(
191 &self,
192 proto_method: &crate::dynamic::proto_parser::ProtoMethod,
193 schemas: &mut serde_json::Map<String, serde_json::Value>,
194 ) -> serde_json::Value {
195 let schema_name = self.get_schema_name(&proto_method.input_type);
196 let input_descriptor_opt = None; let schema_ref = if let Some(descriptor) = input_descriptor_opt {
200 schemas.insert(schema_name.clone(), self.generate_json_schema(&descriptor));
201 serde_json::json!({
202 "$ref": format!("#/components/schemas/{}", schema_name)
203 })
204 } else {
205 serde_json::json!({
207 "type": "object",
208 "description": format!("Protobuf message: {}", proto_method.input_type),
209 "additionalProperties": true
210 })
211 };
212
213 serde_json::json!({
214 "required": true,
215 "content": {
216 "application/json": {
217 "schema": schema_ref,
218 "example": self.generate_example_for_type(&proto_method.input_type)
219 }
220 }
221 })
222 }
223
224 fn generate_openapi_responses_full(
226 &self,
227 service_name: &str,
228 method_name: &str,
229 proto_method: &crate::dynamic::proto_parser::ProtoMethod,
230 schemas: &mut serde_json::Map<String, serde_json::Value>,
231 ) -> serde_json::Value {
232 let schema_name = self.get_schema_name(&proto_method.output_type);
233 let output_descriptor_opt = None; let success_schema = if let Some(descriptor) = output_descriptor_opt {
236 schemas.insert(schema_name.clone(), self.generate_json_schema(&descriptor));
237 serde_json::json!({
238 "$ref": format!("#/components/schemas/{}", schema_name)
239 })
240 } else {
241 serde_json::json!({
243 "type": "object",
244 "description": format!("Protobuf message: {}", proto_method.output_type),
245 "additionalProperties": true
246 })
247 };
248
249 let mut responses = serde_json::json!({
250 "200": {
251 "description": "Successful operation",
252 "content": {
253 "application/json": {
254 "schema": {
255 "type": "object",
256 "properties": {
257 "success": {
258 "type": "boolean",
259 "description": "Whether the request was successful"
260 },
261 "data": success_schema,
262 "error": {
263 "type": ["string", "null"],
264 "description": "Error message if success is false"
265 },
266 "metadata": {
267 "type": "object",
268 "description": "Additional metadata from gRPC response",
269 "additionalProperties": { "type": "string" }
270 }
271 },
272 "required": ["success", "data", "error", "metadata"]
273 },
274 "example": {
275 "success": true,
276 "data": self.generate_example_for_type(&proto_method.output_type),
277 "error": null,
278 "metadata": {
279 "x-mockforge-service": service_name,
280 "x-mockforge-method": method_name
281 }
282 }
283 }
284 }
285 },
286 "400": {
287 "description": "Bad request - invalid JSON or invalid parameters",
288 "content": {
289 "application/json": {
290 "schema": {
291 "type": "object",
292 "properties": {
293 "success": { "type": "boolean" },
294 "data": { "type": "null" },
295 "error": { "type": "string" },
296 "metadata": { "type": "object" }
297 }
298 },
299 "example": {
300 "success": false,
301 "data": null,
302 "error": "Invalid request format",
303 "metadata": {}
304 }
305 }
306 }
307 },
308 "500": {
309 "description": "Internal server error",
310 "content": {
311 "application/json": {
312 "schema": {
313 "type": "object",
314 "properties": {
315 "success": { "type": "boolean" },
316 "data": { "type": "null" },
317 "error": { "type": "string" },
318 "metadata": { "type": "object" }
319 }
320 },
321 "example": {
322 "success": false,
323 "data": null,
324 "error": "Internal server error",
325 "metadata": {}
326 }
327 }
328 }
329 }
330 });
331
332 if proto_method.server_streaming {
334 responses["200"]["content"]["text/event-stream"] = serde_json::json!({
335 "schema": {
336 "type": "string",
337 "description": "Server-sent events stream"
338 },
339 "example": "data: {\"message\":\"Stream started\"}\n\ndata: {\"message\":\"Hello World!\"}\n\ndata: {\"message\":\"Stream ended\"}\n\n"
340 });
341 }
342
343 responses
344 }
345
346 fn get_schema_name(&self, type_name: &str) -> String {
348 let short_name = if let Some(dot_index) = type_name.rfind('.') {
350 &type_name[dot_index + 1..]
351 } else {
352 type_name
353 };
354
355 let schema_name = short_name.to_string();
357
358 if !schema_name.ends_with("Message") {
359 format!("{}Message", schema_name)
360 } else {
361 schema_name
362 }
363 }
364
365 fn generate_example_for_type(&self, type_name: &str) -> serde_json::Value {
367 if type_name.contains("HelloRequest") {
369 serde_json::json!({
370 "name": "World",
371 "user_info": {
372 "user_id": "12345"
373 }
374 })
375 } else if type_name.contains("HelloReply") {
376 serde_json::json!({
377 "message": "Hello World! This is a mock response from MockForge",
378 "timestamp": "2025-01-01T00:00:00Z"
379 })
380 } else {
381 serde_json::json!({
382 "example_field": "example_value"
383 })
384 }
385 }
386
387 pub fn generate_openapi_operation(
389 &self,
390 service_name: &str,
391 method_name: &str,
392 method_descriptor: &prost_reflect::MethodDescriptor,
393 ) -> serde_json::Value {
394 let http_method = self.get_http_method(method_descriptor);
395
396 let mut operation = serde_json::json!({
397 "summary": format!("{} {}", method_name, service_name),
398 "description": format!("Calls the {} method on {} service", method_name, service_name),
399 "parameters": self.generate_openapi_parameters(),
400 });
401
402 if http_method != "get" {
404 operation["requestBody"] = self.generate_openapi_request_body(method_descriptor);
405 }
406
407 operation["responses"] = self.generate_openapi_responses(method_descriptor);
409
410 operation
411 }
412
413 pub fn get_http_method(
415 &self,
416 method_descriptor: &prost_reflect::MethodDescriptor,
417 ) -> &'static str {
418 if method_descriptor.is_client_streaming() && method_descriptor.is_server_streaming() {
419 "post" } else if method_descriptor.is_server_streaming() {
421 "get" } else {
423 "post" }
425 }
426
427 fn generate_openapi_parameters(&self) -> serde_json::Value {
429 serde_json::json!([
430 {
431 "name": "stream",
432 "in": "query",
433 "description": "Streaming mode (none, server, client, bidirectional)",
434 "required": false,
435 "schema": {
436 "type": "string",
437 "enum": ["none", "server", "client", "bidirectional"]
438 }
439 }
440 ])
441 }
442
443 fn generate_openapi_request_body(
445 &self,
446 method_descriptor: &prost_reflect::MethodDescriptor,
447 ) -> serde_json::Value {
448 let input_descriptor = method_descriptor.input();
449
450 serde_json::json!({
451 "required": true,
452 "content": {
453 "application/json": {
454 "schema": self.generate_json_schema(&input_descriptor)
455 }
456 }
457 })
458 }
459
460 fn generate_openapi_responses(
462 &self,
463 method_descriptor: &prost_reflect::MethodDescriptor,
464 ) -> serde_json::Value {
465 let output_descriptor = method_descriptor.output();
466 let success_schema = self.generate_json_schema(&output_descriptor);
467
468 let mut responses = serde_json::json!({
469 "200": {
470 "description": "Successful operation",
471 "content": {
472 "application/json": {
473 "schema": {
474 "type": "object",
475 "properties": {
476 "success": {
477 "type": "boolean",
478 "description": "Whether the request was successful"
479 },
480 "data": success_schema,
481 "error": {
482 "type": ["string", "null"],
483 "description": "Error message if success is false"
484 },
485 "metadata": {
486 "type": "object",
487 "description": "Additional metadata from gRPC response"
488 }
489 },
490 "required": ["success", "data", "error", "metadata"]
491 }
492 }
493 }
494 },
495 "400": {
496 "description": "Bad request - invalid JSON or invalid parameters",
497 "content": {
498 "application/json": {
499 "schema": {
500 "type": "object",
501 "properties": {
502 "success": { "type": "boolean" },
503 "data": { "type": "null" },
504 "error": { "type": "string" },
505 "metadata": { "type": "object" }
506 }
507 }
508 }
509 }
510 },
511 "500": {
512 "description": "Internal server error",
513 "content": {
514 "application/json": {
515 "schema": {
516 "type": "object",
517 "properties": {
518 "success": { "type": "boolean" },
519 "data": { "type": "null" },
520 "error": { "type": "string" },
521 "metadata": { "type": "object" }
522 }
523 }
524 }
525 }
526 }
527 });
528
529 if method_descriptor.is_server_streaming() {
531 responses["200"]["content"]["text/event-stream"] = serde_json::json!({
532 "schema": {
533 "type": "string",
534 "description": "Server-sent events stream"
535 }
536 });
537 }
538
539 responses
540 }
541
542 pub fn generate_json_schema(
544 &self,
545 descriptor: &prost_reflect::MessageDescriptor,
546 ) -> serde_json::Value {
547 let mut properties = serde_json::Map::new();
548 let mut required = Vec::new();
549
550 for field in descriptor.fields() {
551 let field_name = field.name().to_string();
552 let field_schema = self.generate_field_schema(&field);
553
554 properties.insert(field_name.clone(), field_schema);
555
556 if field.supports_presence() && !field.is_list() {
560 let field_name_lower = field.name().to_lowercase();
563 if !field_name_lower.contains("optional") && !field_name_lower.contains("_opt") {
564 required.push(field_name);
565 }
566 }
567 }
568
569 let mut schema = serde_json::json!({
570 "type": "object",
571 "properties": properties
572 });
573
574 if !required.is_empty() {
575 schema["required"] = serde_json::Value::Array(
576 required.into_iter().map(serde_json::Value::String).collect(),
577 );
578 }
579
580 schema
581 }
582
583 fn generate_field_schema(&self, field: &prost_reflect::FieldDescriptor) -> serde_json::Value {
585 let base_type = self.get_json_type_for_field(field);
586
587 if field.is_list() {
588 serde_json::json!({
589 "type": "array",
590 "items": base_type
591 })
592 } else {
593 base_type
594 }
595 }
596
597 fn get_json_type_for_field(&self, field: &prost_reflect::FieldDescriptor) -> serde_json::Value {
599 match field.kind() {
600 prost_reflect::Kind::Message(message_descriptor) => {
601 self.generate_json_schema(&message_descriptor)
602 }
603 prost_reflect::Kind::Enum(_) => {
604 serde_json::json!({
605 "type": "string",
606 "description": "Enum value as string"
607 })
608 }
609 prost_reflect::Kind::String => {
610 serde_json::json!({
611 "type": "string"
612 })
613 }
614 prost_reflect::Kind::Int32
615 | prost_reflect::Kind::Sint32
616 | prost_reflect::Kind::Sfixed32 => {
617 serde_json::json!({
618 "type": "integer",
619 "format": "int32"
620 })
621 }
622 prost_reflect::Kind::Int64
623 | prost_reflect::Kind::Sint64
624 | prost_reflect::Kind::Sfixed64 => {
625 serde_json::json!({
626 "type": "integer",
627 "format": "int64"
628 })
629 }
630 prost_reflect::Kind::Uint32 | prost_reflect::Kind::Fixed32 => {
631 serde_json::json!({
632 "type": "integer",
633 "format": "uint32",
634 "minimum": 0
635 })
636 }
637 prost_reflect::Kind::Uint64 | prost_reflect::Kind::Fixed64 => {
638 serde_json::json!({
639 "type": "integer",
640 "format": "uint64",
641 "minimum": 0
642 })
643 }
644 prost_reflect::Kind::Float => {
645 serde_json::json!({
646 "type": "number",
647 "format": "float"
648 })
649 }
650 prost_reflect::Kind::Double => {
651 serde_json::json!({
652 "type": "number",
653 "format": "double"
654 })
655 }
656 prost_reflect::Kind::Bool => {
657 serde_json::json!({
658 "type": "boolean"
659 })
660 }
661 prost_reflect::Kind::Bytes => {
662 serde_json::json!({
663 "type": "string",
664 "contentEncoding": "base64",
665 "description": "Base64-encoded bytes"
666 })
667 }
668 }
669 }
670
671 pub fn extract_service_name(&self, path: &str) -> Option<String> {
673 if path.starts_with(&self.config.base_path) {
674 let path_without_base = &path[self.config.base_path.len()..];
675 let parts: Vec<&str> = path_without_base.trim_start_matches('/').split('/').collect();
676
677 if parts.len() >= 2 && !parts[0].is_empty() && !parts[1].is_empty() {
678 Some(parts[0].to_string())
679 } else {
680 None
681 }
682 } else {
683 None
684 }
685 }
686
687 pub fn extract_method_name(&self, path: &str) -> Option<String> {
689 if path.starts_with(&self.config.base_path) {
690 let path_without_base = &path[self.config.base_path.len()..];
691 let parts: Vec<&str> = path_without_base.trim_start_matches('/').split('/').collect();
692
693 if parts.len() >= 2 && !parts[0].is_empty() && !parts[1].is_empty() {
694 Some(parts[1].to_string())
695 } else {
696 None
697 }
698 } else {
699 None
700 }
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
709 fn test_clean_service_name() {
710 let config = HttpBridgeConfig::default();
711 let generator = RouteGenerator::new(config);
712
713 assert_eq!(generator.clean_service_name("mockforge.greeter.Greeter"), "greeter");
714 assert_eq!(generator.clean_service_name("MyService"), "myservice");
715 assert_eq!(generator.clean_service_name("My-Service_Name"), "my-service_name");
716 }
717
718 #[test]
719 fn test_clean_method_name() {
720 let config = HttpBridgeConfig::default();
721 let generator = RouteGenerator::new(config);
722
723 assert_eq!(generator.clean_method_name("SayHello"), "sayhello");
724 assert_eq!(generator.clean_method_name("Say_Hello"), "say_hello");
725 assert_eq!(generator.clean_method_name("GetUserData"), "getuserdata");
726 }
727
728 #[test]
729 fn test_generate_route_path() {
730 let config = HttpBridgeConfig {
731 base_path: "/api".to_string(),
732 ..Default::default()
733 };
734 let generator = RouteGenerator::new(config);
735
736 let path = generator.generate_route_path("mockforge.greeter.Greeter", "SayHello");
737 assert_eq!(path, "/api/greeter/sayhello");
738 }
741
742 #[test]
743 fn test_extract_service_name() {
744 let config = HttpBridgeConfig {
745 base_path: "/api".to_string(),
746 ..Default::default()
747 };
748 let generator = RouteGenerator::new(config);
749
750 assert_eq!(
751 generator.extract_service_name("/api/greeter/sayhello"),
752 Some("greeter".to_string())
753 );
754 assert_eq!(generator.extract_service_name("/api/user/get"), Some("user".to_string()));
755 assert_eq!(generator.extract_service_name("/other/path"), None);
756 }
757
758 #[test]
759 fn test_extract_method_name() {
760 let config = HttpBridgeConfig {
761 base_path: "/api".to_string(),
762 ..Default::default()
763 };
764 let generator = RouteGenerator::new(config);
765
766 assert_eq!(
767 generator.extract_method_name("/api/greeter/sayhello"),
768 Some("sayhello".to_string())
769 );
770 assert_eq!(generator.extract_method_name("/api/user/get"), Some("get".to_string()));
771 assert_eq!(generator.extract_method_name("/api/single"), None);
772 }
773
774 #[test]
775 fn test_route_generator_creation() {
776 let config = HttpBridgeConfig {
777 enabled: true,
778 base_path: "/api".to_string(),
779 enable_cors: true,
780 max_request_size: 1024,
781 timeout_seconds: 30,
782 route_pattern: "/{service}/{method}".to_string(),
783 };
784
785 let generator = RouteGenerator::new(config.clone());
786 assert_eq!(generator.config.base_path, "/api");
787 assert_eq!(generator.config.route_pattern, "/{service}/{method}");
788 }
789
790 #[test]
791 fn test_clean_service_name_comprehensive() {
792 let config = HttpBridgeConfig::default();
793 let generator = RouteGenerator::new(config);
794
795 let test_cases = vec![
797 ("simple.Service", "service"),
798 ("com.example.MyService", "myservice"),
799 ("org.test.API", "api"),
800 ("Service", "service"),
801 ("ServiceName", "servicename"),
802 ("service-name", "service-name"),
803 ("service_name", "service_name"),
804 ("service.name", "name"),
805 ("a.b.c.d.Service", "service"),
806 ("Service123", "service123"),
807 ("123Service", "123service"),
808 ("Service-123", "service-123"),
809 ("Service_123", "service_123"),
810 ("Service.Name", "name"),
811 ];
812
813 for (input, expected) in test_cases {
814 assert_eq!(
815 generator.clean_service_name(input),
816 expected,
817 "Failed for input: {}",
818 input
819 );
820 }
821 }
822
823 #[test]
824 fn test_clean_method_name_comprehensive() {
825 let config = HttpBridgeConfig::default();
826 let generator = RouteGenerator::new(config);
827
828 let test_cases = vec![
830 ("GetUser", "getuser"),
831 ("getUser", "getuser"),
832 ("Get_User", "get_user"),
833 ("GetUserData", "getuserdata"),
834 ("getUserData", "getuserdata"),
835 ("GetUser_Data", "getuser_data"),
836 ("method123", "method123"),
837 ("123method", "123method"),
838 ("Method-123", "method-123"),
839 ("Method_123", "method_123"),
840 ("MethodName", "methodname"),
841 ("method_Name", "method_name"),
842 ("method-name", "method-name"),
843 ("method.name", "method.name"),
844 ];
845
846 for (input, expected) in test_cases {
847 assert_eq!(generator.clean_method_name(input), expected, "Failed for input: {}", input);
848 }
849 }
850
851 #[test]
852 fn test_generate_route_path_comprehensive() {
853 let test_configs = vec![
854 ("/api", "/{service}/{method}"),
855 ("/v1", "/{service}/{method}"),
856 ("/api/v1", "/{service}/{method}"),
857 ("/bridge", "/{service}/{method}"),
858 ("", "/{service}/{method}"),
859 ];
860
861 for (base_path, route_pattern) in test_configs {
862 let config = HttpBridgeConfig {
863 base_path: base_path.to_string(),
864 route_pattern: route_pattern.to_string(),
865 ..Default::default()
866 };
867 let generator = RouteGenerator::new(config);
868
869 let test_cases = vec![
870 ("com.example.Greeter", "SayHello"),
871 ("Service", "Method"),
872 ("test.Service", "testMethod"),
873 ("org.example.v1.UserService", "GetUser"),
874 ];
875
876 for (service, method) in test_cases {
877 let path = generator.generate_route_path(service, method);
878 let expected = format!("{}{}", base_path, route_pattern)
879 .replace("{service}", &generator.clean_service_name(service))
880 .replace("{method}", &generator.clean_method_name(method));
881
882 assert_eq!(path, expected, "Failed for service: {}, method: {}", service, method);
883 }
884 }
885 }
886
887 #[test]
888 fn test_generate_url_pattern_comprehensive() {
889 let config = HttpBridgeConfig {
890 base_path: "/api".to_string(),
891 route_pattern: "/{service}/{method}".to_string(),
892 ..Default::default()
893 };
894 let generator = RouteGenerator::new(config);
895
896 let test_cases = vec![
897 ("com.example.Greeter", "SayHello"),
898 ("Service", "Method"),
899 ("test.Service", "testMethod"),
900 ];
901
902 for (service, method) in test_cases {
903 let pattern = generator.generate_url_pattern(service, method);
904 assert!(pattern.starts_with("/api/"), "Pattern should start with /api/: {}", pattern);
905 assert!(
906 pattern.contains("[^/]+"),
907 "Pattern should contain regex for service: {}",
908 pattern
909 );
910 assert!(
911 pattern.contains("[^/]+"),
912 "Pattern should contain regex for method: {}",
913 pattern
914 );
915 }
916 }
917
918 #[test]
919 fn test_extract_service_name_comprehensive() {
920 let config = HttpBridgeConfig {
922 base_path: "/api".to_string(),
923 route_pattern: "/{service}/{method}".to_string(),
924 ..Default::default()
925 };
926 let generator = RouteGenerator::new(config);
927
928 let test_paths = vec![
929 ("/api/greeter/sayhello", Some("greeter".to_string())),
930 ("/api/user/get", Some("user".to_string())),
931 ("/api/complex.service/name", Some("complex.service".to_string())),
932 ("/api/single", None), ("/v1/test/method", None), ("/other/path", None), ("", None),
936 ("/api/", None),
937 ("/api/greeter/", None), ];
939
940 for (path, expected) in test_paths {
941 let result = generator.extract_service_name(path);
942 assert_eq!(result, expected, "Failed for path: {} with base /api", path);
943 }
944
945 let config = HttpBridgeConfig {
947 base_path: "/v1".to_string(),
948 route_pattern: "/{service}/{method}".to_string(),
949 ..Default::default()
950 };
951 let generator = RouteGenerator::new(config);
952
953 let test_paths = vec![
954 ("/v1/test/method", Some("test".to_string())),
955 ("/v1/service/action", Some("service".to_string())),
956 ("/api/greeter/sayhello", None), ];
958
959 for (path, expected) in test_paths {
960 let result = generator.extract_service_name(path);
961 assert_eq!(result, expected, "Failed for path: {} with base /v1", path);
962 }
963 }
964
965 #[test]
966 fn test_extract_method_name_comprehensive() {
967 let config = HttpBridgeConfig {
969 base_path: "/api".to_string(),
970 route_pattern: "/{service}/{method}".to_string(),
971 ..Default::default()
972 };
973 let generator = RouteGenerator::new(config);
974
975 let test_paths = vec![
976 ("/api/greeter/sayhello", Some("sayhello".to_string())),
977 ("/api/user/get", Some("get".to_string())),
978 ("/api/complex.service/method_name", Some("method_name".to_string())),
979 ("/api/single", None), ("/v1/test/method", None), ("/other/path", None), ("", None),
983 ("/api/", None),
984 ("/api/greeter/", None), ];
986
987 for (path, expected) in test_paths {
988 let result = generator.extract_method_name(path);
989 assert_eq!(result, expected, "Failed for path: {} with base /api", path);
990 }
991
992 let config = HttpBridgeConfig {
994 base_path: "/v1".to_string(),
995 route_pattern: "/{service}/{method}".to_string(),
996 ..Default::default()
997 };
998 let generator = RouteGenerator::new(config);
999
1000 let test_paths = vec![
1001 ("/v1/test/method", Some("method".to_string())),
1002 ("/v1/service/action", Some("action".to_string())),
1003 ("/api/greeter/sayhello", None), ];
1005
1006 for (path, expected) in test_paths {
1007 let result = generator.extract_method_name(path);
1008 assert_eq!(result, expected, "Failed for path: {} with base /v1", path);
1009 }
1010 }
1011
1012 #[test]
1013 fn test_get_http_method() {
1014 let config = HttpBridgeConfig::default();
1015 let _generator = RouteGenerator::new(config);
1016
1017 }
1028
1029 #[test]
1030 fn test_generate_openapi_parameters() {
1031 let config = HttpBridgeConfig::default();
1032 let generator = RouteGenerator::new(config);
1033
1034 let params = generator.generate_openapi_parameters();
1035 assert!(params.is_array(), "Parameters should be an array");
1036
1037 if let serde_json::Value::Array(params_array) = params {
1038 assert!(!params_array.is_empty(), "Parameters array should not be empty");
1039
1040 let stream_param = params_array
1042 .iter()
1043 .find(|p| p.get("name").and_then(|n| n.as_str()) == Some("stream"));
1044
1045 assert!(stream_param.is_some(), "Stream parameter should exist");
1046 }
1047 }
1048
1049 #[test]
1050 fn test_get_schema_name() {
1051 let config = HttpBridgeConfig::default();
1052 let generator = RouteGenerator::new(config);
1053
1054 let test_cases = vec![
1055 ("com.example.GetUserRequest", "GetUserRequestMessage"),
1056 ("GetUserRequest", "GetUserRequestMessage"),
1057 ("Request", "RequestMessage"),
1058 ("Response", "ResponseMessage"),
1059 ("Message", "Message"),
1060 ("com.example.v1.GetUserRequest", "GetUserRequestMessage"),
1061 ("org.test.APIRequest", "APIRequestMessage"),
1062 ];
1063
1064 for (input, expected) in test_cases {
1065 let result = generator.get_schema_name(input);
1066 assert_eq!(result, expected, "Failed for input: {}", input);
1067 }
1068 }
1069
1070 #[test]
1071 fn test_generate_example_for_type() {
1072 let config = HttpBridgeConfig::default();
1073 let generator = RouteGenerator::new(config);
1074
1075 let test_types = vec![
1077 "HelloRequest",
1078 "HelloReply",
1079 "GetUserRequest",
1080 "GetUserResponse",
1081 "UnknownMessage",
1082 "TestMessage",
1083 "com.example.TestMessage",
1084 ];
1085
1086 for type_name in test_types {
1087 let example = generator.generate_example_for_type(type_name);
1088 assert!(example.is_object(), "Example should be an object for type: {}", type_name);
1089 assert!(
1090 !example.as_object().unwrap().is_empty(),
1091 "Example object should not be empty for type: {}",
1092 type_name
1093 );
1094 }
1095 }
1096
1097 #[test]
1098 fn test_regex_patterns() {
1099 let config = HttpBridgeConfig::default();
1100 let generator = RouteGenerator::new(config);
1101
1102 let service_test_cases = vec![
1104 ("MyService", "myservice"),
1105 ("My-Service", "my-service"),
1106 ("My_Service", "my_service"),
1107 ("My123Service", "my123service"),
1108 ("My.Service", "service"),
1109 ("My@Service", "my-service"),
1110 ("My#Service", "my-service"),
1111 ("My$Service", "my-service"),
1112 ];
1113
1114 for (input, expected) in service_test_cases {
1115 let cleaned = generator.clean_service_name(input);
1116 assert_eq!(cleaned, expected, "Service name regex failed for: {}", input);
1117 }
1118
1119 let method_test_cases = vec![
1121 ("GetUser", "getuser"),
1122 ("Get-User", "get-user"),
1123 ("Get_User", "get_user"),
1124 ("Get123User", "get123user"),
1125 ("Get.User", "get.user"),
1126 ("Get@User", "get-user"),
1127 ("Get#User", "get-user"),
1128 ("Get$User", "get-user"),
1129 ];
1130
1131 for (input, expected) in method_test_cases {
1132 let cleaned = generator.clean_method_name(input);
1133 assert_eq!(cleaned, expected, "Method name regex failed for: {}", input);
1134 }
1135 }
1136}