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