web_server_abstraction/
mountable.rs

1//! Mountable
2//! This module allows libraries to define web interfaces that can be mounted
3//! into any host application regardless of the underlying web framework.
4//! Now with deep authentication integration for seamless auth across all interfaces.
5
6use crate::auth::{auth_middleware, AuthContext, AuthRequirements};
7use crate::core::{Handler, Route};
8use crate::error::Result;
9use crate::types::{HttpMethod, Request, Response};
10use std::collections::HashMap;
11use std::sync::Arc;
12
13/// A boxed future that returns a Result<Response>
14pub type BoxFuture<T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send + 'static>>;
15
16/// An authenticated route that includes both the route and authentication middleware
17pub struct AuthenticatedRoute {
18    pub route: Route,
19    pub auth_requirements: AuthRequirements,
20    pub auth_context: Arc<AuthContext>,
21}
22
23impl AuthenticatedRoute {
24    /// Create a new authenticated route
25    pub fn new(
26        route: Route,
27        auth_requirements: AuthRequirements,
28        auth_context: Arc<AuthContext>,
29    ) -> Self {
30        Self {
31            route,
32            auth_requirements,
33            auth_context,
34        }
35    }
36
37    /// Execute the route with authentication middleware
38    pub async fn execute(&self, request: Request) -> Result<Response> {
39        // Apply authentication middleware
40        let _user_session =
41            auth_middleware(&self.auth_context, &self.auth_requirements, &request).await?;
42
43        // For now, just return a placeholder response indicating successful auth
44        // In a real implementation, we'd integrate with the Route's handler properly
45        Ok(Response::ok().body("Authenticated route executed"))
46    }
47}
48
49/// A mountable web interface that can be integrated into any host application
50///
51/// This allows libraries to define their web routes and middleware without
52/// knowing what web framework the host application uses or where their
53/// routes will be mounted in the overall routing scheme.
54pub struct MountableInterface {
55    name: String,
56    description: String,
57    routes: Vec<RouteDefinition>,
58    middleware: Vec<String>, // Middleware names for now, could be enhanced
59    mount_options: MountOptions,
60}
61
62/// Route definition for a mountable interface
63pub struct RouteDefinition {
64    /// Relative path within the interface (e.g., "/status", "/api/data")
65    pub path: String,
66    /// HTTP method
67    pub method: HttpMethod,
68    /// Handler function that can be called to create new Route instances
69    pub handler_fn: Arc<dyn Fn() -> Route + Send + Sync>,
70    /// Authentication requirements for this route
71    pub auth_requirements: AuthRequirements,
72    /// Optional description for documentation
73    pub description: Option<String>,
74    /// Tags for organizing routes
75    pub tags: Vec<String>,
76}
77
78/// Configuration options for mounting an interface
79#[derive(Clone, Debug, Default)]
80pub struct MountOptions {
81    /// Whether to strip the mount prefix from request paths
82    pub strip_prefix: bool,
83    /// Whether to add trailing slashes to paths
84    pub add_trailing_slash: bool,
85    /// Custom middleware to apply to all routes in this interface
86    pub middleware: Vec<String>,
87}
88
89/// Builder for creating mountable interfaces
90pub struct InterfaceBuilder {
91    name: String,
92    description: String,
93    routes: Vec<RouteDefinition>,
94    middleware: Vec<String>,
95    mount_options: MountOptions,
96}
97
98impl InterfaceBuilder {
99    /// Create a new interface builder
100    pub fn new(name: impl Into<String>) -> Self {
101        Self {
102            name: name.into(),
103            description: String::new(),
104            routes: Vec::new(),
105            middleware: Vec::new(),
106            mount_options: MountOptions::default(),
107        }
108    }
109
110    /// Set the description for this interface
111    pub fn description(mut self, description: impl Into<String>) -> Self {
112        self.description = description.into();
113        self
114    }
115
116    /// Add a route to this interface
117    pub fn route<H, T>(mut self, path: impl Into<String>, method: HttpMethod, handler: H) -> Self
118    where
119        H: Handler<T> + Clone + Send + Sync + 'static,
120        T: Send + Sync + 'static,
121    {
122        let handler_fn = Arc::new(move || Route::new("", HttpMethod::GET, handler.clone()));
123
124        let route = RouteDefinition {
125            path: path.into(),
126            method,
127            handler_fn,
128            auth_requirements: AuthRequirements::default(),
129            description: None,
130            tags: Vec::new(),
131        };
132        self.routes.push(route);
133        self
134    }
135
136    /// Add a route with authentication requirements
137    pub fn route_with_auth<H, T>(
138        mut self,
139        path: impl Into<String>,
140        method: HttpMethod,
141        handler: H,
142        auth_requirements: AuthRequirements,
143    ) -> Self
144    where
145        H: Handler<T> + Clone + Send + Sync + 'static,
146        T: Send + Sync + 'static,
147    {
148        let handler_fn = Arc::new(move || Route::new("", HttpMethod::GET, handler.clone()));
149
150        let route = RouteDefinition {
151            path: path.into(),
152            method,
153            handler_fn,
154            auth_requirements,
155            description: None,
156            tags: Vec::new(),
157        };
158        self.routes.push(route);
159        self
160    }
161
162    /// Add a route with description and tags
163    pub fn route_with_meta<H, T>(
164        mut self,
165        path: impl Into<String>,
166        method: HttpMethod,
167        handler: H,
168        description: impl Into<String>,
169        tags: Vec<String>,
170    ) -> Self
171    where
172        H: Handler<T> + Clone + Send + Sync + 'static,
173        T: Send + Sync + 'static,
174    {
175        let handler_fn = Arc::new(move || Route::new("", HttpMethod::GET, handler.clone()));
176
177        let route = RouteDefinition {
178            path: path.into(),
179            method,
180            handler_fn,
181            auth_requirements: AuthRequirements::default(),
182            description: Some(description.into()),
183            tags,
184        };
185        self.routes.push(route);
186        self
187    }
188
189    /// Add a route with full configuration (description, tags, and auth)
190    pub fn route_with_full_config<H, T>(
191        mut self,
192        path: impl Into<String>,
193        method: HttpMethod,
194        handler: H,
195        auth_requirements: AuthRequirements,
196        description: impl Into<String>,
197        tags: Vec<String>,
198    ) -> Self
199    where
200        H: Handler<T> + Clone + Send + Sync + 'static,
201        T: Send + Sync + 'static,
202    {
203        let handler_fn = Arc::new(move || Route::new("", HttpMethod::GET, handler.clone()));
204
205        let route = RouteDefinition {
206            path: path.into(),
207            method,
208            handler_fn,
209            auth_requirements,
210            description: Some(description.into()),
211            tags,
212        };
213        self.routes.push(route);
214        self
215    }
216
217    /// Add middleware to this interface
218    pub fn middleware(mut self, middleware: impl Into<String>) -> Self {
219        self.middleware.push(middleware.into());
220        self
221    }
222
223    /// Configure mount options
224    pub fn mount_options(mut self, options: MountOptions) -> Self {
225        self.mount_options = options;
226        self
227    }
228
229    /// Set default authentication requirements for all routes
230    pub fn default_auth_requirements(mut self, auth_requirements: AuthRequirements) -> Self {
231        // Apply the auth requirements to all existing routes that don't have specific requirements
232        for route in &mut self.routes {
233            if !route.auth_requirements.required && route.auth_requirements.permissions.is_empty() {
234                route.auth_requirements = auth_requirements.clone();
235            }
236        }
237        self
238    }
239
240    /// Require authentication for all routes in this interface
241    pub fn require_auth(mut self) -> Self {
242        let auth_requirements = AuthRequirements::required();
243        for route in &mut self.routes {
244            if !route.auth_requirements.required {
245                route.auth_requirements = auth_requirements.clone();
246            }
247        }
248        self
249    }
250
251    /// Require specific permissions for all routes in this interface
252    pub fn require_permissions(mut self, permissions: Vec<String>) -> Self {
253        let auth_requirements = AuthRequirements::required().with_permissions(permissions);
254        for route in &mut self.routes {
255            if route.auth_requirements.permissions.is_empty() {
256                route.auth_requirements = auth_requirements.clone();
257            }
258        }
259        self
260    }
261
262    /// Build the mountable interface
263    pub fn build(self) -> MountableInterface {
264        MountableInterface {
265            name: self.name,
266            description: self.description,
267            routes: self.routes,
268            middleware: self.middleware,
269            mount_options: self.mount_options,
270        }
271    }
272}
273
274impl MountableInterface {
275    /// Create a new interface builder
276    pub fn builder(name: impl Into<String>) -> InterfaceBuilder {
277        InterfaceBuilder::new(name)
278    }
279
280    /// Get the name of this interface
281    pub fn name(&self) -> &str {
282        &self.name
283    }
284
285    /// Get the description of this interface
286    pub fn description(&self) -> &str {
287        &self.description
288    }
289
290    /// Get all routes in this interface
291    pub fn routes(&self) -> &[RouteDefinition] {
292        &self.routes
293    }
294
295    /// Get middleware for this interface
296    pub fn middleware(&self) -> &[String] {
297        &self.middleware
298    }
299
300    /// Get mount options
301    pub fn mount_options(&self) -> &MountOptions {
302        &self.mount_options
303    }
304
305    /// Mount this interface at the given prefix path
306    /// Returns a list of routes with the prefix applied
307    pub fn mount_at(&self, prefix: impl AsRef<str>) -> Result<Vec<Route>> {
308        let prefix = prefix.as_ref();
309        let prefix = if prefix.starts_with('/') {
310            prefix.to_string()
311        } else {
312            format!("/{}", prefix)
313        };
314
315        let mut mounted_routes = Vec::new();
316
317        for route_def in &self.routes {
318            let _full_path = if prefix == "/" {
319                route_def.path.clone()
320            } else {
321                format!("{}{}", prefix, route_def.path)
322            };
323
324            // Create a new route instance using the handler function
325            let route = (route_def.handler_fn)();
326            mounted_routes.push(route);
327        }
328
329        Ok(mounted_routes)
330    }
331
332    /// Mount this interface at the given prefix path with authentication support
333    pub fn mount_with_auth_at(
334        &self,
335        prefix: impl AsRef<str>,
336        auth_context: Arc<AuthContext>,
337    ) -> Result<Vec<AuthenticatedRoute>> {
338        let prefix = prefix.as_ref();
339        let prefix = if prefix.starts_with('/') {
340            prefix.to_string()
341        } else {
342            format!("/{}", prefix)
343        };
344
345        let mut mounted_routes = Vec::new();
346
347        for route_def in &self.routes {
348            let _full_path = if prefix == "/" {
349                route_def.path.clone()
350            } else {
351                format!("{}{}", prefix, route_def.path)
352            };
353
354            // Create a new route instance using the handler function
355            let route = (route_def.handler_fn)();
356
357            // Create an authenticated route with the route's auth requirements
358            let authenticated_route = AuthenticatedRoute::new(
359                route,
360                route_def.auth_requirements.clone(),
361                auth_context.clone(),
362            );
363            mounted_routes.push(authenticated_route);
364        }
365
366        Ok(mounted_routes)
367    }
368
369    /// Generate OpenAPI documentation for this interface
370    pub fn openapi_spec(&self) -> OpenApiSpec {
371        OpenApiSpec {
372            interface_name: self.name.clone(),
373            description: self.description.clone(),
374            routes: self
375                .routes
376                .iter()
377                .map(|r| RouteDoc {
378                    path: r.path.clone(),
379                    method: r.method,
380                    description: r.description.clone(),
381                    tags: r.tags.clone(),
382                })
383                .collect(),
384        }
385    }
386}
387
388/// Registry for managing multiple mountable interfaces
389pub struct InterfaceRegistry {
390    interfaces: HashMap<String, MountableInterface>,
391    auth_context: Option<Arc<AuthContext>>,
392}
393
394impl InterfaceRegistry {
395    /// Create a new interface registry
396    pub fn new() -> Self {
397        Self {
398            interfaces: HashMap::new(),
399            auth_context: None,
400        }
401    }
402
403    /// Create a new interface registry with authentication support
404    pub fn with_auth(auth_context: Arc<AuthContext>) -> Self {
405        Self {
406            interfaces: HashMap::new(),
407            auth_context: Some(auth_context),
408        }
409    }
410
411    /// Set the authentication context
412    pub fn set_auth_context(&mut self, auth_context: Arc<AuthContext>) {
413        self.auth_context = Some(auth_context);
414    }
415
416    /// Get the authentication context
417    pub fn auth_context(&self) -> Option<&Arc<AuthContext>> {
418        self.auth_context.as_ref()
419    }
420
421    /// Register a new interface
422    pub fn register(&mut self, interface: MountableInterface) -> Result<()> {
423        let name = interface.name().to_string();
424        if self.interfaces.contains_key(&name) {
425            return Err(crate::error::WebServerError::ConfigError(format!(
426                "Interface '{}' is already registered",
427                name
428            )));
429        }
430        self.interfaces.insert(name, interface);
431        Ok(())
432    }
433
434    /// Get an interface by name
435    pub fn get(&self, name: &str) -> Option<&MountableInterface> {
436        self.interfaces.get(name)
437    }
438
439    /// List all registered interfaces
440    pub fn list(&self) -> Vec<&str> {
441        self.interfaces.keys().map(|s| s.as_str()).collect()
442    }
443
444    /// Mount an interface at the given prefix with authentication support
445    pub fn mount(
446        &self,
447        interface_name: &str,
448        prefix: impl AsRef<str>,
449    ) -> Result<Vec<AuthenticatedRoute>> {
450        let interface = self.get(interface_name).ok_or_else(|| {
451            crate::error::WebServerError::ConfigError(format!(
452                "Interface '{}' not found",
453                interface_name
454            ))
455        })?;
456
457        let auth_context = self.auth_context.as_ref().ok_or_else(|| {
458            crate::error::WebServerError::ConfigError(
459                "No authentication context available. Use with_auth() or set_auth_context()"
460                    .to_string(),
461            )
462        })?;
463
464        interface.mount_with_auth_at(prefix, auth_context.clone())
465    }
466
467    /// Mount all interfaces with their default paths and authentication
468    pub fn mount_all_with_auth(&self) -> Result<Vec<AuthenticatedRoute>> {
469        let auth_context = self.auth_context.as_ref().ok_or_else(|| {
470            crate::error::WebServerError::ConfigError(
471                "No authentication context available".to_string(),
472            )
473        })?;
474
475        let mut all_routes = Vec::new();
476
477        for (name, interface) in &self.interfaces {
478            let routes =
479                interface.mount_with_auth_at(format!("/{}", name), auth_context.clone())?;
480            all_routes.extend(routes);
481        }
482
483        Ok(all_routes)
484    }
485
486    /// Mount all interfaces with their default paths
487    pub fn mount_all(&self) -> Result<Vec<AuthenticatedRoute>> {
488        let auth_context = self.auth_context.as_ref().ok_or_else(|| {
489            crate::error::WebServerError::ConfigError(
490                "No authentication context available".to_string(),
491            )
492        })?;
493
494        let mut all_routes = Vec::new();
495
496        for (name, interface) in &self.interfaces {
497            let routes =
498                interface.mount_with_auth_at(format!("/{}", name), auth_context.clone())?;
499            all_routes.extend(routes);
500        }
501
502        Ok(all_routes)
503    }
504}
505
506impl Default for InterfaceRegistry {
507    fn default() -> Self {
508        Self::new()
509    }
510}
511
512/// OpenAPI specification for documentation
513#[derive(Debug, Clone)]
514pub struct OpenApiSpec {
515    pub interface_name: String,
516    pub description: String,
517    pub routes: Vec<RouteDoc>,
518}
519
520/// Route documentation for OpenAPI
521#[derive(Debug, Clone)]
522pub struct RouteDoc {
523    pub path: String,
524    pub method: HttpMethod,
525    pub description: Option<String>,
526    pub tags: Vec<String>,
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532    use crate::types::Response;
533
534    async fn hello_handler(_req: crate::types::Request) -> Result<Response> {
535        Ok(Response::ok().body("Hello, World!"))
536    }
537
538    async fn status_handler(_req: crate::types::Request) -> Result<Response> {
539        Response::json(&serde_json::json!({"status": "ok"}))
540    }
541
542    #[test]
543    fn test_interface_builder() {
544        let interface = MountableInterface::builder("test-api")
545            .description("Test API interface")
546            .route("/hello", HttpMethod::GET, hello_handler)
547            .route("/status", HttpMethod::GET, status_handler)
548            .middleware("cors")
549            .build();
550
551        assert_eq!(interface.name(), "test-api");
552        assert_eq!(interface.description(), "Test API interface");
553        assert_eq!(interface.routes().len(), 2);
554        assert_eq!(interface.middleware().len(), 1);
555    }
556
557    #[test]
558    fn test_interface_registry() {
559        let mut registry = InterfaceRegistry::new();
560
561        let interface1 = MountableInterface::builder("api-v1")
562            .route("/users", HttpMethod::GET, hello_handler)
563            .build();
564
565        let interface2 = MountableInterface::builder("admin")
566            .route("/status", HttpMethod::GET, status_handler)
567            .build();
568
569        registry.register(interface1).unwrap();
570        registry.register(interface2).unwrap();
571
572        assert_eq!(registry.list().len(), 2);
573        assert!(registry.get("api-v1").is_some());
574        assert!(registry.get("admin").is_some());
575        assert!(registry.get("nonexistent").is_none());
576    }
577
578    #[tokio::test]
579    async fn test_mounting() {
580        let interface = MountableInterface::builder("test")
581            .route("/hello", HttpMethod::GET, hello_handler)
582            .build();
583
584        let routes = interface.mount_at("/api/v1").unwrap();
585        assert_eq!(routes.len(), 1);
586
587        // Test that the route works (this is a basic test)
588        // In a real implementation, you'd test the full path resolution
589    }
590}