web_server_abstraction/
core.rs

1//! Core traits and types for the web server abstraction.
2
3use crate::error::Result;
4use crate::types::{HttpMethod, Request, Response};
5use async_trait::async_trait;
6use std::future::Future;
7use std::pin::Pin;
8
9/// A boxed future that returns a Result<Response>
10pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
11
12/// A handler function type that takes a request and returns a response
13pub type HandlerFn = Box<dyn Fn(Request) -> BoxFuture<Result<Response>> + Send + Sync>;
14
15/// Trait for middleware components
16#[async_trait]
17pub trait Middleware: Send + Sync {
18    /// Process a request through the middleware
19    async fn call(&self, req: Request, next: Next) -> Result<Response>;
20}
21
22/// Represents the next middleware in the chain
23pub struct Next {
24    handler: Box<dyn Fn(Request) -> BoxFuture<Result<Response>> + Send + Sync>,
25}
26
27impl Next {
28    pub fn new(handler: HandlerFn) -> Self {
29        Self { handler }
30    }
31
32    pub async fn run(self, req: Request) -> Result<Response> {
33        (self.handler)(req).await
34    }
35}
36
37/// A handler that can be converted to a HandlerFn
38pub trait Handler<T>: Clone + Send + Sync + 'static {
39    fn into_handler(self) -> HandlerFn;
40}
41
42/// Implementation for async functions that take a Request and return Result<Response>
43impl<F, Fut> Handler<()> for F
44where
45    F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
46    Fut: Future<Output = Result<Response>> + Send + 'static,
47{
48    fn into_handler(self) -> HandlerFn {
49        Box::new(move |req| {
50            let handler = self.clone();
51            Box::pin(async move { handler(req).await })
52        })
53    }
54}
55
56/// Route definition with advanced routing features
57pub struct Route {
58    pub path: String,
59    pub method: HttpMethod,
60    pub handler: HandlerFn,
61    /// Indicates if this route uses wildcards or path parameters
62    pub is_dynamic: bool,
63}
64
65impl Route {
66    pub fn new<H, T>(path: impl Into<String>, method: HttpMethod, handler: H) -> Self
67    where
68        H: Handler<T>,
69    {
70        let path_str = path.into();
71        let is_dynamic = path_str.contains(':') || path_str.contains('*');
72
73        Self {
74            path: path_str,
75            method,
76            handler: handler.into_handler(),
77            is_dynamic,
78        }
79    }
80
81    /// Check if this route matches a given path
82    pub fn matches(&self, path: &str) -> bool {
83        if !self.is_dynamic {
84            return self.path == path;
85        }
86
87        self.match_dynamic_path(path).is_some()
88    }
89
90    /// Extract path parameters from a matching path
91    pub fn extract_params(&self, path: &str) -> std::collections::HashMap<String, String> {
92        self.match_dynamic_path(path).unwrap_or_default()
93    }
94
95    /// Match dynamic path patterns
96    fn match_dynamic_path(&self, path: &str) -> Option<std::collections::HashMap<String, String>> {
97        let route_parts: Vec<&str> = self.path.split('/').collect();
98        let path_parts: Vec<&str> = path.split('/').collect();
99
100        if route_parts.len() != path_parts.len() {
101            // Handle wildcard at the end
102            if let Some(last_part) = route_parts.last()
103                && last_part.starts_with('*')
104                && route_parts.len() <= path_parts.len()
105            {
106                // Wildcard matches remaining path
107                let mut params = std::collections::HashMap::new();
108                let param_name = last_part.trim_start_matches('*');
109                if !param_name.is_empty() {
110                    let remaining_path = path_parts[route_parts.len() - 1..].join("/");
111                    params.insert(param_name.to_string(), remaining_path);
112                }
113                return Some(params);
114            }
115            return None;
116        }
117
118        let mut params = std::collections::HashMap::new();
119
120        for (route_part, path_part) in route_parts.iter().zip(path_parts.iter()) {
121            if route_part.starts_with(':') {
122                // Path parameter
123                let param_name = route_part.trim_start_matches(':');
124                params.insert(param_name.to_string(), path_part.to_string());
125            } else if route_part.starts_with('*') {
126                // Wildcard
127                let param_name = route_part.trim_start_matches('*');
128                if !param_name.is_empty() {
129                    params.insert(param_name.to_string(), path_part.to_string());
130                }
131            } else if route_part != path_part {
132                // Exact match required
133                return None;
134            }
135        }
136
137        Some(params)
138    }
139}
140
141// Since we can't use trait objects easily, we'll use an enum for different adapters
142pub enum AdapterType {
143    Mock(crate::adapters::mock::MockAdapter),
144    #[cfg(feature = "axum")]
145    Axum(crate::adapters::axum::AxumAdapter),
146    #[cfg(feature = "actix-web")]
147    ActixWeb(crate::adapters::actix_web::ActixWebAdapter),
148    #[cfg(feature = "warp")]
149    Warp(crate::adapters::warp::WarpAdapter),
150    #[cfg(feature = "rocket")]
151    Rocket(crate::adapters::rocket::RocketAdapter),
152    #[cfg(feature = "salvo")]
153    Salvo(crate::adapters::salvo::SalvoAdapter),
154    #[cfg(feature = "poem")]
155    Poem(crate::adapters::poem::PoemAdapter),
156}
157
158/// Main web server struct that uses an adapter
159pub struct WebServer {
160    adapter: AdapterType,
161    routes: Vec<Route>,
162    middleware: Vec<Box<dyn Middleware>>,
163}
164
165impl Default for WebServer {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171impl WebServer {
172    /// Create a new web server with the mock adapter (for testing)
173    pub fn new() -> Self {
174        Self::with_mock_adapter()
175    }
176
177    /// Create a new web server with the mock adapter
178    pub fn with_mock_adapter() -> Self {
179        Self {
180            adapter: AdapterType::Mock(crate::adapters::mock::MockAdapter::new()),
181            routes: Vec::new(),
182            middleware: Vec::new(),
183        }
184    }
185
186    /// Create a new web server with the Axum adapter
187    #[cfg(feature = "axum")]
188    pub fn with_axum_adapter() -> Self {
189        Self {
190            adapter: AdapterType::Axum(crate::adapters::axum::AxumAdapter::new()),
191            routes: Vec::new(),
192            middleware: Vec::new(),
193        }
194    }
195
196    /// Create a new web server with the Actix-Web adapter
197    #[cfg(feature = "actix-web")]
198    pub fn with_actix_web_adapter() -> Self {
199        Self {
200            adapter: AdapterType::ActixWeb(crate::adapters::actix_web::ActixWebAdapter::new()),
201            routes: Vec::new(),
202            middleware: Vec::new(),
203        }
204    }
205
206    /// Create a new web server with the Warp adapter
207    #[cfg(feature = "warp")]
208    pub fn with_warp_adapter() -> Self {
209        Self {
210            adapter: AdapterType::Warp(crate::adapters::warp::WarpAdapter::new()),
211            routes: Vec::new(),
212            middleware: Vec::new(),
213        }
214    }
215
216    // Note: The following adapter constructors are work-in-progress:
217
218    /// Create a new web server with the Rocket adapter
219    #[cfg(feature = "rocket")]
220    pub fn with_rocket_adapter() -> Self {
221        Self {
222            adapter: AdapterType::Rocket(crate::adapters::rocket::RocketAdapter::new()),
223            routes: Vec::new(),
224            middleware: Vec::new(),
225        }
226    }
227
228    /// Create a new web server with the Salvo adapter
229    #[cfg(feature = "salvo")]
230    pub fn with_salvo_adapter() -> Self {
231        Self {
232            adapter: AdapterType::Salvo(crate::adapters::salvo::SalvoAdapter::new()),
233            routes: Vec::new(),
234            middleware: Vec::new(),
235        }
236    }
237
238    /// Create a new web server with the Poem adapter
239    #[cfg(feature = "poem")]
240    pub fn with_poem_adapter() -> Self {
241        Self {
242            adapter: AdapterType::Poem(crate::adapters::poem::PoemAdapter::new()),
243            routes: Vec::new(),
244            middleware: Vec::new(),
245        }
246    }
247
248    /// Add a route to the server
249    pub fn route<H, T>(mut self, path: impl Into<String>, method: HttpMethod, handler: H) -> Self
250    where
251        H: Handler<T>,
252    {
253        let route = Route::new(path, method, handler);
254        self.routes.push(route);
255        self
256    }
257
258    /// Add middleware to the server
259    pub fn middleware<M>(mut self, middleware: M) -> Self
260    where
261        M: Middleware + 'static,
262    {
263        self.middleware.push(Box::new(middleware));
264        self
265    }
266
267    /// Enable automatic path parameter extraction
268    /// This adds middleware that automatically extracts path parameters
269    /// from routes and makes them available via req.param()
270    pub fn with_path_params(mut self) -> Self {
271        // Extract route patterns for parameter matching
272        let route_patterns: Vec<(String, HttpMethod)> = self
273            .routes
274            .iter()
275            .map(|r| (r.path.clone(), r.method))
276            .collect();
277
278        let param_middleware = crate::middleware::PathParameterMiddleware::new(route_patterns);
279        self.middleware.push(Box::new(param_middleware));
280        self
281    }
282
283    /// Add a WebSocket route to the server
284    /// Note: This is a basic implementation. Full WebSocket support requires
285    /// framework-specific handling in each adapter.
286    pub fn websocket(mut self, path: impl Into<String>) -> Self {
287        // Add a placeholder route that indicates WebSocket upgrade capability
288        let websocket_handler = |req: crate::types::Request| async move {
289            // Check if this is a WebSocket upgrade request
290            if req
291                .headers
292                .get("Upgrade")
293                .is_some_and(|v| v.to_lowercase() == "websocket")
294            {
295                // Create proper WebSocket upgrade with correct accept key
296                match crate::types::WebSocketUpgrade::from_request(req) {
297                    Ok(upgrade) => {
298                        let accept_key = upgrade.generate_accept_key();
299                        Ok(crate::types::Response::new(
300                            crate::types::StatusCode::SWITCHING_PROTOCOLS,
301                        )
302                        .header("Upgrade", "websocket")
303                        .header("Connection", "Upgrade")
304                        .header("Sec-WebSocket-Accept", accept_key))
305                    }
306                    Err(e) => Err(e),
307                }
308            } else {
309                Err(crate::error::WebServerError::custom(
310                    "Not a WebSocket upgrade request",
311                ))
312            }
313        };
314
315        self.routes.push(Route::new(
316            path,
317            crate::types::HttpMethod::GET,
318            websocket_handler,
319        ));
320        self
321    }
322
323    /// Convenience method for GET routes
324    pub fn get<H, T>(self, path: impl Into<String>, handler: H) -> Self
325    where
326        H: Handler<T>,
327    {
328        self.route(path, crate::types::HttpMethod::GET, handler)
329    }
330
331    /// Convenience method for POST routes
332    pub fn post<H, T>(self, path: impl Into<String>, handler: H) -> Self
333    where
334        H: Handler<T>,
335    {
336        self.route(path, crate::types::HttpMethod::POST, handler)
337    }
338
339    /// Convenience method for PUT routes
340    pub fn put<H, T>(self, path: impl Into<String>, handler: H) -> Self
341    where
342        H: Handler<T>,
343    {
344        self.route(path, crate::types::HttpMethod::PUT, handler)
345    }
346
347    /// Convenience method for DELETE routes
348    pub fn delete<H, T>(self, path: impl Into<String>, handler: H) -> Self
349    where
350        H: Handler<T>,
351    {
352        self.route(path, crate::types::HttpMethod::DELETE, handler)
353    }
354
355    /// Convenience method for PATCH routes
356    pub fn patch<H, T>(self, path: impl Into<String>, handler: H) -> Self
357    where
358        H: Handler<T>,
359    {
360        self.route(path, crate::types::HttpMethod::PATCH, handler)
361    }
362
363    /// Convenience method for HEAD routes
364    pub fn head<H, T>(self, path: impl Into<String>, handler: H) -> Self
365    where
366        H: Handler<T>,
367    {
368        self.route(path, crate::types::HttpMethod::HEAD, handler)
369    }
370
371    /// Convenience method for OPTIONS routes
372    pub fn options<H, T>(self, path: impl Into<String>, handler: H) -> Self
373    where
374        H: Handler<T>,
375    {
376        self.route(path, crate::types::HttpMethod::OPTIONS, handler)
377    }
378
379    /// Convenience method for TRACE routes
380    pub fn trace<H, T>(self, path: impl Into<String>, handler: H) -> Self
381    where
382        H: Handler<T>,
383    {
384        self.route(path, crate::types::HttpMethod::TRACE, handler)
385    }
386
387    /// Convenience method for CONNECT routes
388    pub fn connect<H, T>(self, path: impl Into<String>, handler: H) -> Self
389    where
390        H: Handler<T>,
391    {
392        self.route(path, crate::types::HttpMethod::CONNECT, handler)
393    }
394
395    /// Add a route with path parameters (e.g., "/users/:id")
396    pub fn param_route<H, T>(self, path: impl Into<String>, method: HttpMethod, handler: H) -> Self
397    where
398        H: Handler<T>,
399    {
400        // This is the same as route() but with clear intention for parameterized paths
401        self.route(path, method, handler)
402    }
403
404    /// Add a wildcard route (e.g., "/static/*file")
405    pub fn wildcard_route<H, T>(
406        self,
407        path: impl Into<String>,
408        method: HttpMethod,
409        handler: H,
410    ) -> Self
411    where
412        H: Handler<T>,
413    {
414        // This is the same as route() but with clear intention for wildcard paths
415        self.route(path, method, handler)
416    }
417
418    /// Bind the server to an address
419    pub async fn bind(mut self, addr: &str) -> Result<BoundServer> {
420        // Apply all routes and middleware to the adapter
421        match &mut self.adapter {
422            AdapterType::Mock(adapter) => {
423                for route in self.routes {
424                    adapter.route(&route.path, route.method, route.handler);
425                }
426                for middleware in self.middleware {
427                    adapter.middleware(middleware);
428                }
429                adapter.bind(addr).await?;
430            }
431            #[cfg(feature = "axum")]
432            AdapterType::Axum(adapter) => {
433                for route in self.routes {
434                    adapter.route(&route.path, route.method, route.handler);
435                }
436                for middleware in self.middleware {
437                    adapter.middleware(middleware);
438                }
439                adapter.bind(addr).await?;
440            }
441            #[cfg(feature = "actix-web")]
442            AdapterType::ActixWeb(adapter) => {
443                for route in self.routes {
444                    adapter.route(&route.path, route.method, route.handler);
445                }
446                for middleware in self.middleware {
447                    adapter.middleware(middleware);
448                }
449                adapter.bind(addr).await?;
450            }
451            #[cfg(feature = "warp")]
452            AdapterType::Warp(adapter) => {
453                for route in self.routes {
454                    adapter.route(&route.path, route.method, route.handler);
455                }
456                for middleware in self.middleware {
457                    adapter.middleware(middleware);
458                }
459                adapter.bind(addr).await?;
460            }
461            #[cfg(feature = "rocket")]
462            AdapterType::Rocket(adapter) => {
463                for route in self.routes {
464                    adapter.route(&route.path, route.method, route.handler);
465                }
466                for middleware in self.middleware {
467                    adapter.middleware(middleware);
468                }
469                adapter.bind(addr).await?;
470            }
471            #[cfg(feature = "salvo")]
472            AdapterType::Salvo(adapter) => {
473                for route in self.routes {
474                    adapter.route(&route.path, route.method, route.handler);
475                }
476                for middleware in self.middleware {
477                    adapter.middleware(middleware);
478                }
479                adapter.bind(addr).await?;
480            }
481            #[cfg(feature = "poem")]
482            AdapterType::Poem(adapter) => {
483                for route in self.routes {
484                    adapter.route(&route.path, route.method, route.handler);
485                }
486                for middleware in self.middleware {
487                    adapter.middleware(middleware);
488                }
489                adapter.bind(addr).await?;
490            }
491        }
492
493        Ok(BoundServer {
494            adapter: self.adapter,
495        })
496    }
497}
498
499/// A bound server ready to run
500pub struct BoundServer {
501    adapter: AdapterType,
502}
503
504impl BoundServer {
505    /// Run the server
506    pub async fn run(self) -> Result<()> {
507        match self.adapter {
508            AdapterType::Mock(adapter) => adapter.run().await,
509            #[cfg(feature = "axum")]
510            AdapterType::Axum(adapter) => adapter.run().await,
511            #[cfg(feature = "actix-web")]
512            AdapterType::ActixWeb(adapter) => adapter.run().await,
513            #[cfg(feature = "warp")]
514            AdapterType::Warp(adapter) => adapter.run().await,
515            #[cfg(feature = "rocket")]
516            AdapterType::Rocket(adapter) => adapter.run().await,
517            #[cfg(feature = "salvo")]
518            AdapterType::Salvo(adapter) => adapter.run().await,
519            #[cfg(feature = "poem")]
520            AdapterType::Poem(adapter) => adapter.run().await,
521        }
522    }
523}
524
525// Temporarily disabled until tests are updated
526// #[cfg(test)]
527// mod tests;