allframe_core/router/
mod.rs

1//! # Protocol-Agnostic Router
2//!
3//! Write handlers once, expose them via REST, GraphQL, and gRPC.
4//!
5//! This is AllFrame's core differentiator - the same handler can serve
6//! multiple protocols without code changes.
7//!
8//! ## Quick Start
9//!
10//! ```rust
11//! use allframe_core::router::{Router, RestAdapter, GraphQLAdapter, GrpcAdapter};
12//!
13//! // Create router and register handlers
14//! let mut router = Router::new();
15//! router.register("get_user", || async {
16//!     r#"{"id": 42, "name": "Alice"}"#.to_string()
17//! });
18//!
19//! // Expose via REST
20//! let mut rest = RestAdapter::new();
21//! rest.route("GET", "/users/:id", "get_user");
22//!
23//! // Expose via GraphQL
24//! let mut graphql = GraphQLAdapter::new();
25//! graphql.query("user", "get_user");
26//!
27//! // Expose via gRPC
28//! let mut grpc = GrpcAdapter::new();
29//! grpc.unary("UserService", "GetUser", "get_user");
30//! ```
31//!
32//! ## Key Types
33//!
34//! - `Router` - Central handler registry
35//! - `RestAdapter` - REST protocol adapter
36//! - `GraphQLAdapter` - GraphQL protocol adapter
37//! - `GrpcAdapter` - gRPC protocol adapter
38//! - `ProtocolAdapter` - Trait for custom protocol adapters
39//!
40//! ## API Documentation
41//!
42//! Generate beautiful API documentation automatically:
43//!
44//! - `scalar_html` - Scalar UI for REST APIs (<50KB)
45//! - `graphiql_html` - GraphiQL playground for GraphQL
46//! - `grpc_explorer_html` - gRPC Explorer for gRPC services
47//! - `OpenApiGenerator` - OpenAPI 3.1 spec generation
48//!
49//! ## Configuration-Driven Protocol Selection
50//!
51//! Use TOML configuration to select protocols without code changes:
52//!
53//! ```toml
54//! [server]
55//! protocols = ["rest", "graphql", "grpc"]
56//!
57//! [server.rest]
58//! port = 8080
59//!
60//! [server.graphql]
61//! port = 8081
62//! ```
63
64use std::{collections::HashMap, future::Future};
65
66pub mod adapter;
67pub mod builder;
68#[cfg(feature = "router")]
69pub mod config;
70pub mod contract;
71pub mod docs;
72pub mod graphiql;
73pub mod graphql;
74pub mod grpc;
75pub mod grpc_explorer;
76pub mod handler;
77pub mod metadata;
78pub mod method;
79pub mod openapi;
80pub mod rest;
81pub mod scalar;
82pub mod schema;
83
84// Production adapters (optional features)
85#[cfg(feature = "router-graphql")]
86pub mod graphql_prod;
87#[cfg(feature = "router-grpc")]
88pub mod grpc_prod;
89
90pub use adapter::ProtocolAdapter;
91pub use builder::RouteBuilder;
92#[cfg(feature = "router")]
93pub use config::{GraphQLConfig, GrpcConfig, RestConfig, RouterConfig, ServerConfig};
94pub use contract::{
95    ContractTestConfig, ContractTestResult, ContractTestResults, ContractTestable, ContractTester,
96};
97pub use docs::DocsConfig;
98pub use graphiql::{graphiql_html, GraphiQLConfig, GraphiQLTheme};
99pub use graphql::{GraphQLAdapter, GraphQLOperation, OperationType};
100// Re-export production adapters when features are enabled
101#[cfg(feature = "router-graphql")]
102pub use graphql_prod::GraphQLProductionAdapter;
103pub use grpc::{GrpcAdapter, GrpcMethod, GrpcMethodType, GrpcRequest, GrpcStatus};
104pub use grpc_explorer::{grpc_explorer_html, GrpcExplorerConfig, GrpcExplorerTheme};
105#[cfg(feature = "router-grpc")]
106pub use grpc_prod::{protobuf, status, streaming, GrpcProductionAdapter, GrpcService};
107pub use handler::{Handler, HandlerFn};
108pub use metadata::RouteMetadata;
109pub use method::Method;
110pub use openapi::{OpenApiGenerator, OpenApiServer};
111pub use rest::{RestAdapter, RestRequest, RestResponse, RestRoute};
112pub use scalar::{scalar_html, ScalarConfig, ScalarLayout, ScalarTheme};
113pub use schema::ToJsonSchema;
114
115/// Router manages handler registration and protocol adapters
116///
117/// The router allows you to register handlers once and expose them via
118/// multiple protocols based on configuration.
119pub struct Router {
120    handlers: HashMap<String, Box<dyn Handler>>,
121    adapters: HashMap<String, Box<dyn ProtocolAdapter>>,
122    routes: Vec<RouteMetadata>,
123    #[cfg(feature = "router")]
124    #[allow(dead_code)]
125    config: Option<RouterConfig>,
126}
127
128impl Router {
129    /// Create a new router
130    pub fn new() -> Self {
131        Self {
132            handlers: HashMap::new(),
133            adapters: HashMap::new(),
134            routes: Vec::new(),
135            #[cfg(feature = "router")]
136            config: None,
137        }
138    }
139
140    /// Create a new router with configuration
141    #[cfg(feature = "router")]
142    pub fn with_config(config: RouterConfig) -> Self {
143        let mut router = Self {
144            handlers: HashMap::new(),
145            adapters: HashMap::new(),
146            routes: Vec::new(),
147            config: Some(config.clone()),
148        };
149
150        // Auto-register adapters based on config
151        if config.has_protocol("rest") {
152            router.add_adapter(Box::new(RestAdapter::new()));
153        }
154        if config.has_protocol("graphql") {
155            router.add_adapter(Box::new(GraphQLAdapter::new()));
156        }
157        if config.has_protocol("grpc") {
158            router.add_adapter(Box::new(GrpcAdapter::new()));
159        }
160
161        router
162    }
163
164    /// Register a handler with a name
165    pub fn register<F, Fut>(&mut self, name: &str, handler: F)
166    where
167        F: Fn() -> Fut + Send + Sync + 'static,
168        Fut: Future<Output = String> + Send + 'static,
169    {
170        self.handlers
171            .insert(name.to_string(), Box::new(HandlerFn::new(handler)));
172    }
173
174    /// Get the number of registered handlers
175    pub fn handlers_count(&self) -> usize {
176        self.handlers.len()
177    }
178
179    /// Add a protocol adapter
180    pub fn add_adapter(&mut self, adapter: Box<dyn ProtocolAdapter>) {
181        self.adapters.insert(adapter.name().to_string(), adapter);
182    }
183
184    /// Check if an adapter is registered
185    pub fn has_adapter(&self, name: &str) -> bool {
186        self.adapters.contains_key(name)
187    }
188
189    /// Get an adapter by name
190    pub fn get_adapter(&self, name: &str) -> Option<&dyn ProtocolAdapter> {
191        self.adapters.get(name).map(|b| &**b)
192    }
193
194    /// Route a request through the appropriate protocol adapter
195    pub async fn route_request(&self, protocol: &str, request: &str) -> Result<String, String> {
196        let adapter = self
197            .get_adapter(protocol)
198            .ok_or_else(|| format!("Adapter not found: {}", protocol))?;
199
200        adapter.handle(request).await
201    }
202
203    /// Execute a handler by name
204    pub async fn execute(&self, name: &str) -> Result<String, String> {
205        match self.handlers.get(name) {
206            Some(handler) => handler.call().await,
207            None => Err(format!("Handler '{}' not found", name)),
208        }
209    }
210
211    /// List all registered handler names
212    ///
213    /// Returns a vector of all handler names that have been registered
214    /// with this router. Used by MCP server for tool discovery.
215    pub fn list_handlers(&self) -> Vec<String> {
216        self.handlers.keys().cloned().collect()
217    }
218
219    /// Call a handler by name with request data
220    ///
221    /// This is an alias for `execute()` that provides a more explicit
222    /// API for directly calling handlers. Used by MCP server.
223    pub async fn call_handler(&self, name: &str, _request: &str) -> Result<String, String> {
224        // For now, ignore request parameter and just execute the handler
225        // In Phase 2, we'll add proper parameter parsing
226        self.execute(name).await
227    }
228
229    /// Check if handler can be called via REST
230    pub fn can_handle_rest(&self, _name: &str) -> bool {
231        self.has_adapter("rest")
232    }
233
234    /// Check if handler can be called via GraphQL
235    pub fn can_handle_graphql(&self, _name: &str) -> bool {
236        self.has_adapter("graphql")
237    }
238
239    /// Check if handler can be called via gRPC
240    pub fn can_handle_grpc(&self, _name: &str) -> bool {
241        self.has_adapter("grpc")
242    }
243
244    /// Get list of enabled protocols
245    pub fn enabled_protocols(&self) -> Vec<String> {
246        self.adapters.keys().cloned().collect()
247    }
248
249    /// Add a route with metadata
250    ///
251    /// This stores route metadata that can be used to generate
252    /// documentation (OpenAPI, GraphQL schemas, gRPC reflection).
253    pub fn add_route(&mut self, metadata: RouteMetadata) {
254        self.routes.push(metadata);
255    }
256
257    /// Get all registered routes
258    ///
259    /// Returns an immutable reference to all route metadata.
260    /// This is used for documentation generation.
261    pub fn routes(&self) -> &[RouteMetadata] {
262        &self.routes
263    }
264
265    /// Register a GET route
266    ///
267    /// This is a convenience method that registers both a handler and route
268    /// metadata for a GET request. The handler name is automatically
269    /// generated as "GET:{path}".
270    pub fn get<F, Fut>(&mut self, path: &str, handler: F)
271    where
272        F: Fn() -> Fut + Send + Sync + 'static,
273        Fut: Future<Output = String> + Send + 'static,
274    {
275        let handler_name = format!("GET:{}", path);
276        self.register(&handler_name, handler);
277        self.add_route(RouteMetadata::new(path, Method::GET, "rest"));
278    }
279
280    /// Register a POST route
281    ///
282    /// This is a convenience method that registers both a handler and route
283    /// metadata for a POST request. The handler name is automatically
284    /// generated as "POST:{path}".
285    pub fn post<F, Fut>(&mut self, path: &str, handler: F)
286    where
287        F: Fn() -> Fut + Send + Sync + 'static,
288        Fut: Future<Output = String> + Send + 'static,
289    {
290        let handler_name = format!("POST:{}", path);
291        self.register(&handler_name, handler);
292        self.add_route(RouteMetadata::new(path, Method::POST, "rest"));
293    }
294
295    /// Register a PUT route
296    ///
297    /// This is a convenience method that registers both a handler and route
298    /// metadata for a PUT request. The handler name is automatically
299    /// generated as "PUT:{path}".
300    pub fn put<F, Fut>(&mut self, path: &str, handler: F)
301    where
302        F: Fn() -> Fut + Send + Sync + 'static,
303        Fut: Future<Output = String> + Send + 'static,
304    {
305        let handler_name = format!("PUT:{}", path);
306        self.register(&handler_name, handler);
307        self.add_route(RouteMetadata::new(path, Method::PUT, "rest"));
308    }
309
310    /// Register a DELETE route
311    ///
312    /// This is a convenience method that registers both a handler and route
313    /// metadata for a DELETE request. The handler name is automatically
314    /// generated as "DELETE:{path}".
315    pub fn delete<F, Fut>(&mut self, path: &str, handler: F)
316    where
317        F: Fn() -> Fut + Send + Sync + 'static,
318        Fut: Future<Output = String> + Send + 'static,
319    {
320        let handler_name = format!("DELETE:{}", path);
321        self.register(&handler_name, handler);
322        self.add_route(RouteMetadata::new(path, Method::DELETE, "rest"));
323    }
324
325    /// Register a PATCH route
326    ///
327    /// This is a convenience method that registers both a handler and route
328    /// metadata for a PATCH request. The handler name is automatically
329    /// generated as "PATCH:{path}".
330    pub fn patch<F, Fut>(&mut self, path: &str, handler: F)
331    where
332        F: Fn() -> Fut + Send + Sync + 'static,
333        Fut: Future<Output = String> + Send + 'static,
334    {
335        let handler_name = format!("PATCH:{}", path);
336        self.register(&handler_name, handler);
337        self.add_route(RouteMetadata::new(path, Method::PATCH, "rest"));
338    }
339
340    /// Register a HEAD route
341    ///
342    /// This is a convenience method that registers both a handler and route
343    /// metadata for a HEAD request. The handler name is automatically
344    /// generated as "HEAD:{path}".
345    pub fn head<F, Fut>(&mut self, path: &str, handler: F)
346    where
347        F: Fn() -> Fut + Send + Sync + 'static,
348        Fut: Future<Output = String> + Send + 'static,
349    {
350        let handler_name = format!("HEAD:{}", path);
351        self.register(&handler_name, handler);
352        self.add_route(RouteMetadata::new(path, Method::HEAD, "rest"));
353    }
354
355    /// Register an OPTIONS route
356    ///
357    /// This is a convenience method that registers both a handler and route
358    /// metadata for an OPTIONS request. The handler name is automatically
359    /// generated as "OPTIONS:{path}".
360    pub fn options<F, Fut>(&mut self, path: &str, handler: F)
361    where
362        F: Fn() -> Fut + Send + Sync + 'static,
363        Fut: Future<Output = String> + Send + 'static,
364    {
365        let handler_name = format!("OPTIONS:{}", path);
366        self.register(&handler_name, handler);
367        self.add_route(RouteMetadata::new(path, Method::OPTIONS, "rest"));
368    }
369
370    /// Call handler via REST
371    pub async fn call_rest(&self, method: &str, path: &str) -> Result<String, String> {
372        let adapter = self
373            .adapters
374            .get("rest")
375            .ok_or_else(|| "REST adapter not enabled".to_string())?;
376
377        let request = format!("{} {}", method, path);
378        adapter.handle(&request).await
379    }
380
381    /// Call handler via GraphQL
382    pub async fn call_graphql(&self, query: &str) -> Result<String, String> {
383        let adapter = self
384            .adapters
385            .get("graphql")
386            .ok_or_else(|| "GraphQL adapter not enabled".to_string())?;
387
388        adapter.handle(query).await
389    }
390
391    /// Call handler via gRPC
392    pub async fn call_grpc(&self, method: &str, request: &str) -> Result<String, String> {
393        let adapter = self
394            .adapters
395            .get("grpc")
396            .ok_or_else(|| "gRPC adapter not enabled".to_string())?;
397
398        let grpc_request = format!("{}:{}", method, request);
399        adapter.handle(&grpc_request).await
400    }
401
402    /// Generate Scalar documentation HTML with default configuration
403    ///
404    /// This is a convenience method that generates a complete HTML page
405    /// with Scalar UI for interactive API documentation.
406    ///
407    /// # Arguments
408    ///
409    /// * `title` - API title
410    /// * `version` - API version
411    ///
412    /// # Example
413    ///
414    /// ```rust
415    /// use allframe_core::router::Router;
416    ///
417    /// let mut router = Router::new();
418    /// router.get("/users", || async { "Users".to_string() });
419    ///
420    /// let html = router.scalar("My API", "1.0.0");
421    /// // Serve this HTML at /docs endpoint
422    /// ```
423    pub fn scalar(&self, title: &str, version: &str) -> String {
424        let config = scalar::ScalarConfig::default();
425        self.scalar_docs(config, title, version)
426    }
427
428    /// Generate Scalar documentation HTML with custom configuration
429    ///
430    /// This method allows full customization of the Scalar UI appearance
431    /// and behavior.
432    ///
433    /// # Arguments
434    ///
435    /// * `config` - Scalar configuration
436    /// * `title` - API title
437    /// * `version` - API version
438    ///
439    /// # Example
440    ///
441    /// ```rust
442    /// use allframe_core::router::{Router, ScalarConfig, ScalarTheme};
443    ///
444    /// let mut router = Router::new();
445    /// router.get("/users", || async { "Users".to_string() });
446    ///
447    /// let config = ScalarConfig::new()
448    ///     .theme(ScalarTheme::Light)
449    ///     .show_sidebar(false);
450    ///
451    /// let html = router.scalar_docs(config, "My API", "1.0.0");
452    /// ```
453    pub fn scalar_docs(&self, config: scalar::ScalarConfig, title: &str, version: &str) -> String {
454        // Generate OpenAPI spec
455        let spec = OpenApiGenerator::new(title, version).generate(self);
456        let spec_json = serde_json::to_string(&spec).unwrap_or_else(|_| "{}".to_string());
457
458        // Generate Scalar HTML
459        scalar::scalar_html(&config, title, &spec_json)
460    }
461}
462
463impl Default for Router {
464    fn default() -> Self {
465        Self::new()
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472
473    #[tokio::test]
474    async fn test_router_creation() {
475        let router = Router::new();
476        assert_eq!(router.handlers_count(), 0);
477    }
478
479    #[tokio::test]
480    async fn test_handler_registration() {
481        let mut router = Router::new();
482        router.register("test", || async { "Hello".to_string() });
483        assert_eq!(router.handlers_count(), 1);
484    }
485
486    #[tokio::test]
487    async fn test_handler_execution() {
488        let mut router = Router::new();
489        router.register("test", || async { "Hello".to_string() });
490        let result = router.execute("test").await;
491        assert_eq!(result, Ok("Hello".to_string()));
492    }
493
494    // New tests for route metadata tracking
495    #[tokio::test]
496    async fn test_router_starts_with_no_routes() {
497        let router = Router::new();
498        let routes = router.routes();
499        assert_eq!(routes.len(), 0);
500    }
501
502    #[tokio::test]
503    async fn test_add_route_metadata() {
504        let mut router = Router::new();
505        let metadata = RouteMetadata::new("/users", "GET", "rest");
506
507        router.add_route(metadata.clone());
508
509        let routes = router.routes();
510        assert_eq!(routes.len(), 1);
511        assert_eq!(routes[0].path, "/users");
512        assert_eq!(routes[0].method, "GET");
513        assert_eq!(routes[0].protocol, "rest");
514    }
515
516    #[tokio::test]
517    async fn test_add_multiple_routes() {
518        let mut router = Router::new();
519
520        router.add_route(RouteMetadata::new("/users", "GET", "rest"));
521        router.add_route(RouteMetadata::new("/users", "POST", "rest"));
522        router.add_route(RouteMetadata::new("/posts", "GET", "rest"));
523
524        let routes = router.routes();
525        assert_eq!(routes.len(), 3);
526    }
527
528    #[tokio::test]
529    async fn test_routes_with_different_protocols() {
530        let mut router = Router::new();
531
532        router.add_route(RouteMetadata::new("/users", "GET", "rest"));
533        router.add_route(RouteMetadata::new("users", "query", "graphql"));
534        router.add_route(RouteMetadata::new("UserService.GetUser", "unary", "grpc"));
535
536        let routes = router.routes();
537        assert_eq!(routes.len(), 3);
538
539        assert_eq!(routes[0].protocol, "rest");
540        assert_eq!(routes[1].protocol, "graphql");
541        assert_eq!(routes[2].protocol, "grpc");
542    }
543
544    #[tokio::test]
545    async fn test_routes_returns_immutable_reference() {
546        let mut router = Router::new();
547        router.add_route(RouteMetadata::new("/test", "GET", "rest"));
548
549        let routes1 = router.routes();
550        let routes2 = router.routes();
551
552        // Both should have the same data
553        assert_eq!(routes1.len(), routes2.len());
554        assert_eq!(routes1[0].path, routes2[0].path);
555    }
556
557    // Tests for type-safe route registration
558    #[tokio::test]
559    async fn test_route_get_method() {
560        let mut router = Router::new();
561        router.get("/users", || async { "User list".to_string() });
562
563        let routes = router.routes();
564        assert_eq!(routes.len(), 1);
565        assert_eq!(routes[0].path, "/users");
566        assert_eq!(routes[0].method, "GET");
567        assert_eq!(routes[0].protocol, "rest");
568    }
569
570    #[tokio::test]
571    async fn test_route_post_method() {
572        let mut router = Router::new();
573        router.post("/users", || async { "User created".to_string() });
574
575        let routes = router.routes();
576        assert_eq!(routes.len(), 1);
577        assert_eq!(routes[0].path, "/users");
578        assert_eq!(routes[0].method, "POST");
579        assert_eq!(routes[0].protocol, "rest");
580    }
581
582    #[tokio::test]
583    async fn test_route_put_method() {
584        let mut router = Router::new();
585        router.put("/users/1", || async { "User updated".to_string() });
586
587        let routes = router.routes();
588        assert_eq!(routes.len(), 1);
589        assert_eq!(routes[0].method, "PUT");
590    }
591
592    #[tokio::test]
593    async fn test_route_delete_method() {
594        let mut router = Router::new();
595        router.delete("/users/1", || async { "User deleted".to_string() });
596
597        let routes = router.routes();
598        assert_eq!(routes.len(), 1);
599        assert_eq!(routes[0].method, "DELETE");
600    }
601
602    #[tokio::test]
603    async fn test_route_patch_method() {
604        let mut router = Router::new();
605        router.patch("/users/1", || async { "User patched".to_string() });
606
607        let routes = router.routes();
608        assert_eq!(routes.len(), 1);
609        assert_eq!(routes[0].method, "PATCH");
610    }
611
612    #[tokio::test]
613    async fn test_multiple_routes_different_methods() {
614        let mut router = Router::new();
615        router.get("/users", || async { "List".to_string() });
616        router.post("/users", || async { "Create".to_string() });
617        router.put("/users/1", || async { "Update".to_string() });
618        router.delete("/users/1", || async { "Delete".to_string() });
619
620        let routes = router.routes();
621        assert_eq!(routes.len(), 4);
622
623        assert_eq!(routes[0].method, "GET");
624        assert_eq!(routes[1].method, "POST");
625        assert_eq!(routes[2].method, "PUT");
626        assert_eq!(routes[3].method, "DELETE");
627    }
628
629    #[tokio::test]
630    async fn test_route_method_with_path_params() {
631        let mut router = Router::new();
632        router.get("/users/{id}", || async { "User details".to_string() });
633        router.get("/users/{id}/posts/{post_id}", || async {
634            "Post details".to_string()
635        });
636
637        let routes = router.routes();
638        assert_eq!(routes.len(), 2);
639        assert_eq!(routes[0].path, "/users/{id}");
640        assert_eq!(routes[1].path, "/users/{id}/posts/{post_id}");
641    }
642
643    #[tokio::test]
644    async fn test_route_registration_and_execution() {
645        let mut router = Router::new();
646        router.get("/test", || async { "GET response".to_string() });
647        router.post("/test", || async { "POST response".to_string() });
648
649        // Both route metadata and handler should be registered
650        assert_eq!(router.routes().len(), 2);
651        assert_eq!(router.handlers_count(), 2);
652
653        // Handlers should be executable
654        let result1 = router.execute("GET:/test").await;
655        let result2 = router.execute("POST:/test").await;
656
657        assert_eq!(result1, Ok("GET response".to_string()));
658        assert_eq!(result2, Ok("POST response".to_string()));
659    }
660
661    // Tests for Scalar integration
662    #[tokio::test]
663    async fn test_scalar_generates_html() {
664        let mut router = Router::new();
665        router.get("/users", || async { "Users".to_string() });
666
667        let html = router.scalar("Test API", "1.0.0");
668
669        assert!(html.contains("<!DOCTYPE html>"));
670        assert!(html.contains("<title>Test API - API Documentation</title>"));
671        assert!(html.contains("@scalar/api-reference"));
672    }
673
674    #[tokio::test]
675    async fn test_scalar_contains_openapi_spec() {
676        let mut router = Router::new();
677        router.get("/users", || async { "Users".to_string() });
678        router.post("/users", || async { "User created".to_string() });
679
680        let html = router.scalar("Test API", "1.0.0");
681
682        // Should contain the OpenAPI spec
683        assert!(html.contains("openapi"));
684        assert!(html.contains("Test API"));
685        assert!(html.contains("1.0.0"));
686    }
687
688    #[tokio::test]
689    async fn test_scalar_docs_with_custom_config() {
690        let mut router = Router::new();
691        router.get("/users", || async { "Users".to_string() });
692
693        let config = scalar::ScalarConfig::new()
694            .theme(scalar::ScalarTheme::Light)
695            .show_sidebar(false);
696
697        let html = router.scalar_docs(config, "Custom API", "2.0.0");
698
699        assert!(html.contains("Custom API"));
700        assert!(html.contains(r#""theme":"light""#));
701        assert!(html.contains(r#""showSidebar":false"#));
702    }
703
704    #[tokio::test]
705    async fn test_scalar_docs_with_custom_css() {
706        let mut router = Router::new();
707        router.get("/test", || async { "Test".to_string() });
708
709        let config = scalar::ScalarConfig::new().custom_css("body { font-family: 'Inter'; }");
710
711        let html = router.scalar_docs(config, "API", "1.0");
712
713        assert!(html.contains("<style>body { font-family: 'Inter'; }</style>"));
714    }
715
716    #[tokio::test]
717    async fn test_scalar_with_multiple_routes() {
718        let mut router = Router::new();
719        router.get("/users", || async { "Users".to_string() });
720        router.post("/users", || async { "Create".to_string() });
721        router.get("/users/{id}", || async { "User details".to_string() });
722        router.delete("/users/{id}", || async { "Delete".to_string() });
723
724        let html = router.scalar("API", "1.0.0");
725
726        // Should contain all routes in the OpenAPI spec
727        assert!(html.contains("/users"));
728    }
729
730    // Tests for protocol adapter management
731    #[tokio::test]
732    async fn test_get_adapter_returns_adapter() {
733        let mut router = Router::new();
734        router.add_adapter(Box::new(RestAdapter::new()));
735
736        let adapter = router.get_adapter("rest");
737        assert!(adapter.is_some());
738        assert_eq!(adapter.unwrap().name(), "rest");
739    }
740
741    #[tokio::test]
742    async fn test_get_adapter_returns_none_for_missing() {
743        let router = Router::new();
744        let adapter = router.get_adapter("rest");
745        assert!(adapter.is_none());
746    }
747
748    #[tokio::test]
749    async fn test_route_request_success() {
750        let mut router = Router::new();
751        router.register("test_handler", || async { "Success!".to_string() });
752
753        // Register adapter with a route
754        let mut rest_adapter = RestAdapter::new();
755        rest_adapter.route("GET", "/test", "test_handler");
756        router.add_adapter(Box::new(rest_adapter));
757
758        let result = router.route_request("rest", "GET /test").await;
759        assert!(result.is_ok());
760        let response = result.unwrap();
761        assert!(response.contains("HTTP 200") || response.contains("test_handler"));
762    }
763
764    #[tokio::test]
765    async fn test_route_request_unknown_adapter() {
766        let router = Router::new();
767        let result = router.route_request("unknown", "test").await;
768        assert!(result.is_err());
769        assert!(result.unwrap_err().contains("Adapter not found"));
770    }
771
772    #[tokio::test]
773    async fn test_enabled_protocols_empty() {
774        let router = Router::new();
775        let protocols = router.enabled_protocols();
776        assert_eq!(protocols.len(), 0);
777    }
778
779    #[tokio::test]
780    async fn test_enabled_protocols_multiple() {
781        let mut router = Router::new();
782        router.add_adapter(Box::new(RestAdapter::new()));
783        router.add_adapter(Box::new(GraphQLAdapter::new()));
784        router.add_adapter(Box::new(GrpcAdapter::new()));
785
786        let protocols = router.enabled_protocols();
787        assert_eq!(protocols.len(), 3);
788        assert!(protocols.contains(&"rest".to_string()));
789        assert!(protocols.contains(&"graphql".to_string()));
790        assert!(protocols.contains(&"grpc".to_string()));
791    }
792
793    #[tokio::test]
794    async fn test_can_handle_rest() {
795        let mut router = Router::new();
796        assert!(!router.can_handle_rest("test"));
797
798        router.add_adapter(Box::new(RestAdapter::new()));
799        assert!(router.can_handle_rest("test"));
800    }
801
802    #[tokio::test]
803    async fn test_can_handle_graphql() {
804        let mut router = Router::new();
805        assert!(!router.can_handle_graphql("test"));
806
807        router.add_adapter(Box::new(GraphQLAdapter::new()));
808        assert!(router.can_handle_graphql("test"));
809    }
810
811    #[tokio::test]
812    async fn test_can_handle_grpc() {
813        let mut router = Router::new();
814        assert!(!router.can_handle_grpc("test"));
815
816        router.add_adapter(Box::new(GrpcAdapter::new()));
817        assert!(router.can_handle_grpc("test"));
818    }
819
820    // ===== Integration Tests: Multi-Protocol Routing =====
821
822    #[tokio::test]
823    async fn test_integration_single_handler_rest() {
824        // Test: Single handler exposed via REST
825        let mut router = Router::new();
826        router.register("get_user", || async { "User data".to_string() });
827
828        // Configure REST adapter
829        let mut rest = RestAdapter::new();
830        rest.route("GET", "/users/:id", "get_user");
831        router.add_adapter(Box::new(rest));
832
833        // Route REST request
834        let response = router.route_request("rest", "GET /users/42").await;
835        assert!(response.is_ok());
836        assert!(response.unwrap().contains("get_user"));
837    }
838
839    #[tokio::test]
840    async fn test_integration_single_handler_graphql() {
841        // Test: Single handler exposed via GraphQL
842        let mut router = Router::new();
843        router.register("get_user", || async { "User data".to_string() });
844
845        // Configure GraphQL adapter
846        let mut graphql = GraphQLAdapter::new();
847        graphql.query("user", "get_user");
848        router.add_adapter(Box::new(graphql));
849
850        // Route GraphQL request
851        let response = router.route_request("graphql", "query { user }").await;
852        assert!(response.is_ok());
853        assert!(response.unwrap().contains("get_user"));
854    }
855
856    #[tokio::test]
857    async fn test_integration_single_handler_grpc() {
858        // Test: Single handler exposed via gRPC
859        let mut router = Router::new();
860        router.register("get_user", || async { "User data".to_string() });
861
862        // Configure gRPC adapter
863        let mut grpc = GrpcAdapter::new();
864        grpc.unary("UserService", "GetUser", "get_user");
865        router.add_adapter(Box::new(grpc));
866
867        // Route gRPC request
868        let response = router
869            .route_request("grpc", "UserService.GetUser:{\"id\":42}")
870            .await;
871        assert!(response.is_ok());
872        assert!(response.unwrap().contains("get_user"));
873    }
874
875    #[tokio::test]
876    async fn test_integration_single_handler_all_protocols() {
877        // Test: Single handler exposed via ALL protocols
878        let mut router = Router::new();
879        router.register("get_user", || async { "User data".to_string() });
880
881        // Configure REST adapter
882        let mut rest = RestAdapter::new();
883        rest.route("GET", "/users/:id", "get_user");
884        router.add_adapter(Box::new(rest));
885
886        // Configure GraphQL adapter
887        let mut graphql = GraphQLAdapter::new();
888        graphql.query("user", "get_user");
889        router.add_adapter(Box::new(graphql));
890
891        // Configure gRPC adapter
892        let mut grpc = GrpcAdapter::new();
893        grpc.unary("UserService", "GetUser", "get_user");
894        router.add_adapter(Box::new(grpc));
895
896        // Test REST
897        let rest_response = router.route_request("rest", "GET /users/42").await;
898        assert!(rest_response.is_ok());
899        assert!(rest_response.unwrap().contains("get_user"));
900
901        // Test GraphQL
902        let graphql_response = router.route_request("graphql", "query { user }").await;
903        assert!(graphql_response.is_ok());
904        assert!(graphql_response.unwrap().contains("get_user"));
905
906        // Test gRPC
907        let grpc_response = router
908            .route_request("grpc", "UserService.GetUser:{\"id\":42}")
909            .await;
910        assert!(grpc_response.is_ok());
911        assert!(grpc_response.unwrap().contains("get_user"));
912    }
913
914    #[tokio::test]
915    async fn test_integration_multiple_handlers_all_protocols() {
916        // Test: Multiple handlers, each exposed via all protocols
917        let mut router = Router::new();
918        router.register("get_user", || async { "User data".to_string() });
919        router.register("list_users", || async { "Users list".to_string() });
920        router.register("create_user", || async { "Created user".to_string() });
921
922        // Configure REST adapter
923        let mut rest = RestAdapter::new();
924        rest.route("GET", "/users/:id", "get_user");
925        rest.route("GET", "/users", "list_users");
926        rest.route("POST", "/users", "create_user");
927        router.add_adapter(Box::new(rest));
928
929        // Configure GraphQL adapter
930        let mut graphql = GraphQLAdapter::new();
931        graphql.query("user", "get_user");
932        graphql.query("users", "list_users");
933        graphql.mutation("createUser", "create_user");
934        router.add_adapter(Box::new(graphql));
935
936        // Configure gRPC adapter
937        let mut grpc = GrpcAdapter::new();
938        grpc.unary("UserService", "GetUser", "get_user");
939        grpc.unary("UserService", "ListUsers", "list_users");
940        grpc.unary("UserService", "CreateUser", "create_user");
941        router.add_adapter(Box::new(grpc));
942
943        // Test each handler via each protocol
944        assert!(router
945            .route_request("rest", "GET /users/42")
946            .await
947            .unwrap()
948            .contains("get_user"));
949        assert!(router
950            .route_request("graphql", "query { user }")
951            .await
952            .unwrap()
953            .contains("get_user"));
954        assert!(router
955            .route_request("grpc", "UserService.GetUser:{}")
956            .await
957            .unwrap()
958            .contains("get_user"));
959    }
960
961    #[tokio::test]
962    async fn test_integration_error_handling_rest_404() {
963        // Test: REST 404 error
964        let mut router = Router::new();
965
966        let mut rest = RestAdapter::new();
967        rest.route("GET", "/users/:id", "get_user");
968        router.add_adapter(Box::new(rest));
969
970        let response = router.route_request("rest", "GET /posts/42").await;
971        assert!(response.is_ok());
972        assert!(response.unwrap().contains("HTTP 404"));
973    }
974
975    #[tokio::test]
976    async fn test_integration_error_handling_graphql_not_found() {
977        // Test: GraphQL operation not found
978        let mut router = Router::new();
979
980        let mut graphql = GraphQLAdapter::new();
981        graphql.query("user", "get_user");
982        router.add_adapter(Box::new(graphql));
983
984        let response = router.route_request("graphql", "query { post }").await;
985        assert!(response.is_ok());
986        assert!(response.unwrap().contains("errors"));
987    }
988
989    #[tokio::test]
990    async fn test_integration_error_handling_grpc_unimplemented() {
991        // Test: gRPC method not implemented
992        let mut router = Router::new();
993
994        let mut grpc = GrpcAdapter::new();
995        grpc.unary("UserService", "GetUser", "get_user");
996        router.add_adapter(Box::new(grpc));
997
998        let response = router.route_request("grpc", "UserService.GetPost:{}").await;
999        assert!(response.is_ok());
1000        assert!(response.unwrap().contains("grpc-status: 12")); // UNIMPLEMENTED
1001    }
1002
1003    #[tokio::test]
1004    async fn test_integration_unknown_protocol() {
1005        // Test: Unknown protocol error
1006        let router = Router::new();
1007
1008        let response = router.route_request("unknown", "request").await;
1009        assert!(response.is_err());
1010        assert!(response.unwrap_err().contains("Adapter not found"));
1011    }
1012
1013    #[tokio::test]
1014    async fn test_integration_protocol_specific_features_rest_methods() {
1015        // Test: REST-specific HTTP methods
1016        let mut router = Router::new();
1017        router.register("get_users", || async { "Users".to_string() });
1018        router.register("create_user", || async { "Created".to_string() });
1019        router.register("update_user", || async { "Updated".to_string() });
1020        router.register("delete_user", || async { "Deleted".to_string() });
1021
1022        let mut rest = RestAdapter::new();
1023        rest.route("GET", "/users", "get_users");
1024        rest.route("POST", "/users", "create_user");
1025        rest.route("PUT", "/users/:id", "update_user");
1026        rest.route("DELETE", "/users/:id", "delete_user");
1027        router.add_adapter(Box::new(rest));
1028
1029        // Test different HTTP methods
1030        assert!(router
1031            .route_request("rest", "GET /users")
1032            .await
1033            .unwrap()
1034            .contains("get_users"));
1035        assert!(router
1036            .route_request("rest", "POST /users")
1037            .await
1038            .unwrap()
1039            .contains("create_user"));
1040        assert!(router
1041            .route_request("rest", "PUT /users/42")
1042            .await
1043            .unwrap()
1044            .contains("update_user"));
1045        assert!(router
1046            .route_request("rest", "DELETE /users/42")
1047            .await
1048            .unwrap()
1049            .contains("delete_user"));
1050    }
1051
1052    #[tokio::test]
1053    async fn test_integration_protocol_specific_features_graphql_types() {
1054        // Test: GraphQL-specific query vs mutation
1055        let mut router = Router::new();
1056        router.register("get_user", || async { "User".to_string() });
1057        router.register("create_user", || async { "Created".to_string() });
1058
1059        let mut graphql = GraphQLAdapter::new();
1060        graphql.query("user", "get_user");
1061        graphql.mutation("createUser", "create_user");
1062        router.add_adapter(Box::new(graphql));
1063
1064        // Test query
1065        assert!(router
1066            .route_request("graphql", "query { user }")
1067            .await
1068            .unwrap()
1069            .contains("get_user"));
1070
1071        // Test mutation
1072        assert!(router
1073            .route_request("graphql", "mutation { createUser }")
1074            .await
1075            .unwrap()
1076            .contains("create_user"));
1077    }
1078
1079    #[tokio::test]
1080    async fn test_integration_protocol_specific_features_grpc_streaming() {
1081        // Test: gRPC-specific streaming modes
1082        let mut router = Router::new();
1083        router.register("get_user", || async { "User".to_string() });
1084        router.register("list_users", || async { "Users".to_string() });
1085
1086        let mut grpc = GrpcAdapter::new();
1087        grpc.unary("UserService", "GetUser", "get_user");
1088        grpc.server_streaming("UserService", "ListUsers", "list_users");
1089        router.add_adapter(Box::new(grpc));
1090
1091        // Test unary
1092        let unary_response = router
1093            .route_request("grpc", "UserService.GetUser:{}")
1094            .await
1095            .unwrap();
1096        assert!(unary_response.contains("unary"));
1097
1098        // Test server streaming
1099        let streaming_response = router
1100            .route_request("grpc", "UserService.ListUsers:{}")
1101            .await
1102            .unwrap();
1103        assert!(streaming_response.contains("server_streaming"));
1104    }
1105}