wasm_runner_sdk/
router.rs

1//! HTTP router for dispatching requests to handlers.
2//!
3//! Provides a simple, ergonomic router with support for path parameters.
4//!
5//! # Example
6//!
7//! ```ignore
8//! use wasm_runner_sdk::prelude::*;
9//!
10//! fn get_user(Param(id): Param<String>) -> impl IntoResponse {
11//!     Json(serde_json::json!({ "userId": id }))
12//! }
13//!
14//! fn create_user(Json(body): Json<CreateUser>) -> impl IntoResponse {
15//!     (StatusCode::CREATED, Json(body))
16//! }
17//!
18//! let router = Router::new()
19//!     .get("/users/:id", get_user)
20//!     .post("/users", create_user)
21//!     .not_found(|| StatusCode::NOT_FOUND);
22//!
23//! #[unsafe(no_mangle)]
24//! pub extern "C" fn _start() {
25//!     router.run();
26//! }
27//! ```
28
29use crate::extract::{ExtractError, FromRequest};
30use crate::handler::Handler;
31use crate::request::{Method, Request};
32use crate::response::{IntoResponse, Response, StatusCode};
33use std::collections::HashMap;
34
35/// A path parameter extracted from the URL.
36///
37/// # Example
38///
39/// ```ignore
40/// // For route "/users/:id" and request "/users/123"
41/// fn handler(Param(id): Param<String>) -> impl IntoResponse {
42///     format!("User ID: {}", id)
43/// }
44/// ```
45#[derive(Debug, Clone)]
46pub struct Param<T>(pub T);
47
48/// Multiple path parameters extracted from the URL.
49///
50/// # Example
51///
52/// ```ignore
53/// // For route "/users/:user_id/posts/:post_id"
54/// fn handler(Params(params): Params) -> impl IntoResponse {
55///     let user_id = params.get("user_id").unwrap();
56///     let post_id = params.get("post_id").unwrap();
57///     format!("User: {}, Post: {}", user_id, post_id)
58/// }
59/// ```
60#[derive(Debug, Clone, Default)]
61pub struct Params(pub HashMap<String, String>);
62
63impl Params {
64    /// Gets a parameter by name.
65    pub fn get(&self, name: &str) -> Option<&str> {
66        self.0.get(name).map(|s| s.as_str())
67    }
68
69    /// Gets a parameter by name, parsing it to the desired type.
70    pub fn get_parse<T: std::str::FromStr>(&self, name: &str) -> Option<T> {
71        self.0.get(name).and_then(|s| s.parse().ok())
72    }
73}
74
75// Thread-local storage for route parameters during request handling
76std::thread_local! {
77    static CURRENT_PARAMS: std::cell::RefCell<HashMap<String, String>> = std::cell::RefCell::new(HashMap::new());
78}
79
80fn set_current_params(params: HashMap<String, String>) {
81    CURRENT_PARAMS.with(|p| {
82        *p.borrow_mut() = params;
83    });
84}
85
86fn get_current_params() -> HashMap<String, String> {
87    CURRENT_PARAMS.with(|p| p.borrow().clone())
88}
89
90impl FromRequest for Params {
91    type Error = ExtractError;
92
93    fn from_request(_req: &mut Request) -> Result<Self, Self::Error> {
94        Ok(Params(get_current_params()))
95    }
96}
97
98impl FromRequest for Param<String> {
99    type Error = ExtractError;
100
101    fn from_request(_req: &mut Request) -> Result<Self, Self::Error> {
102        let params = get_current_params();
103        // Return the first parameter value
104        params
105            .into_values()
106            .next()
107            .map(Param)
108            .ok_or_else(|| ExtractError::bad_request("No path parameter found"))
109    }
110}
111
112impl FromRequest for Param<i64> {
113    type Error = ExtractError;
114
115    fn from_request(_req: &mut Request) -> Result<Self, Self::Error> {
116        let params = get_current_params();
117        params
118            .into_values()
119            .next()
120            .ok_or_else(|| ExtractError::bad_request("No path parameter found"))?
121            .parse()
122            .map(Param)
123            .map_err(|_| ExtractError::bad_request("Invalid integer parameter"))
124    }
125}
126
127impl FromRequest for Param<u64> {
128    type Error = ExtractError;
129
130    fn from_request(_req: &mut Request) -> Result<Self, Self::Error> {
131        let params = get_current_params();
132        params
133            .into_values()
134            .next()
135            .ok_or_else(|| ExtractError::bad_request("No path parameter found"))?
136            .parse()
137            .map(Param)
138            .map_err(|_| ExtractError::bad_request("Invalid unsigned integer parameter"))
139    }
140}
141
142/// A boxed handler function that can be stored in the router.
143type BoxedHandler = Box<dyn Fn(&mut Request) -> Response>;
144
145/// A route pattern with parameter extraction.
146#[derive(Clone)]
147struct RoutePattern {
148    segments: Vec<PatternSegment>,
149}
150
151#[derive(Clone)]
152enum PatternSegment {
153    /// Literal segment that must match exactly.
154    Literal(String),
155    /// Parameter segment that captures a value.
156    Param(String),
157    /// Wildcard that matches any remaining path.
158    Wildcard,
159}
160
161impl RoutePattern {
162    fn parse(pattern: &str) -> Self {
163        let segments = pattern
164            .split('/')
165            .filter(|s| !s.is_empty())
166            .map(|s| {
167                if s == "*" {
168                    PatternSegment::Wildcard
169                } else if let Some(name) = s.strip_prefix(':') {
170                    PatternSegment::Param(name.to_string())
171                } else {
172                    PatternSegment::Literal(s.to_string())
173                }
174            })
175            .collect();
176
177        RoutePattern { segments }
178    }
179
180    fn matches(&self, path: &str) -> Option<HashMap<String, String>> {
181        let path_segments: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
182        let mut params = HashMap::new();
183        let mut path_idx = 0;
184
185        for (pattern_idx, segment) in self.segments.iter().enumerate() {
186            match segment {
187                PatternSegment::Literal(lit) => {
188                    if path_idx >= path_segments.len() || path_segments[path_idx] != lit {
189                        return None;
190                    }
191                    path_idx += 1;
192                }
193                PatternSegment::Param(name) => {
194                    if path_idx >= path_segments.len() {
195                        return None;
196                    }
197                    params.insert(name.clone(), path_segments[path_idx].to_string());
198                    path_idx += 1;
199                }
200                PatternSegment::Wildcard => {
201                    // Wildcard matches rest of path - must be last segment
202                    if pattern_idx == self.segments.len() - 1 {
203                        return Some(params);
204                    }
205                    return None;
206                }
207            }
208        }
209
210        // All path segments must be consumed (unless wildcard)
211        if path_idx == path_segments.len() {
212            Some(params)
213        } else {
214            None
215        }
216    }
217}
218
219/// A route entry combining method, pattern, and handler.
220struct Route {
221    method: Option<Method>, // None means any method
222    pattern: RoutePattern,
223    handler: BoxedHandler,
224}
225
226/// HTTP router for dispatching requests to handlers.
227///
228/// # Example
229///
230/// ```ignore
231/// let router = Router::new()
232///     .get("/", || "Hello, World!")
233///     .get("/users", list_users)
234///     .get("/users/:id", get_user)
235///     .post("/users", create_user)
236///     .put("/users/:id", update_user)
237///     .delete("/users/:id", delete_user);
238/// ```
239pub struct Router {
240    routes: Vec<Route>,
241    not_found_handler: Option<BoxedHandler>,
242    method_not_allowed_handler: Option<BoxedHandler>,
243}
244
245impl Default for Router {
246    fn default() -> Self {
247        Self::new()
248    }
249}
250
251impl Router {
252    /// Creates a new empty router.
253    pub fn new() -> Self {
254        Router {
255            routes: Vec::new(),
256            not_found_handler: None,
257            method_not_allowed_handler: None,
258        }
259    }
260
261    /// Adds a route that matches any HTTP method.
262    pub fn route<H, Args>(self, pattern: &str, handler: H) -> Self
263    where
264        H: Handler<Args> + 'static,
265        H::Output: IntoResponse,
266    {
267        self.add_route(None, pattern, handler)
268    }
269
270    /// Adds a GET route.
271    pub fn get<H, Args>(self, pattern: &str, handler: H) -> Self
272    where
273        H: Handler<Args> + 'static,
274        H::Output: IntoResponse,
275    {
276        self.add_route(Some(Method::Get), pattern, handler)
277    }
278
279    /// Adds a POST route.
280    pub fn post<H, Args>(self, pattern: &str, handler: H) -> Self
281    where
282        H: Handler<Args> + 'static,
283        H::Output: IntoResponse,
284    {
285        self.add_route(Some(Method::Post), pattern, handler)
286    }
287
288    /// Adds a PUT route.
289    pub fn put<H, Args>(self, pattern: &str, handler: H) -> Self
290    where
291        H: Handler<Args> + 'static,
292        H::Output: IntoResponse,
293    {
294        self.add_route(Some(Method::Put), pattern, handler)
295    }
296
297    /// Adds a DELETE route.
298    pub fn delete<H, Args>(self, pattern: &str, handler: H) -> Self
299    where
300        H: Handler<Args> + 'static,
301        H::Output: IntoResponse,
302    {
303        self.add_route(Some(Method::Delete), pattern, handler)
304    }
305
306    /// Adds a PATCH route.
307    pub fn patch<H, Args>(self, pattern: &str, handler: H) -> Self
308    where
309        H: Handler<Args> + 'static,
310        H::Output: IntoResponse,
311    {
312        self.add_route(Some(Method::Patch), pattern, handler)
313    }
314
315    /// Adds a HEAD route.
316    pub fn head<H, Args>(self, pattern: &str, handler: H) -> Self
317    where
318        H: Handler<Args> + 'static,
319        H::Output: IntoResponse,
320    {
321        self.add_route(Some(Method::Head), pattern, handler)
322    }
323
324    /// Adds an OPTIONS route.
325    pub fn options<H, Args>(self, pattern: &str, handler: H) -> Self
326    where
327        H: Handler<Args> + 'static,
328        H::Output: IntoResponse,
329    {
330        self.add_route(Some(Method::Options), pattern, handler)
331    }
332
333    /// Sets a custom 404 Not Found handler.
334    pub fn not_found<H, Args>(mut self, handler: H) -> Self
335    where
336        H: Handler<Args> + 'static,
337        H::Output: IntoResponse,
338    {
339        let handler_cell = std::cell::RefCell::new(Some(handler));
340        self.not_found_handler = Some(Box::new(move |req| {
341            if let Some(h) = handler_cell.borrow_mut().take() {
342                h.call(req).into_response()
343            } else {
344                Response::new(StatusCode::INTERNAL_SERVER_ERROR)
345                    .with_text("Handler already consumed")
346            }
347        }));
348        self
349    }
350
351    /// Sets a custom 405 Method Not Allowed handler.
352    pub fn method_not_allowed<H, Args>(mut self, handler: H) -> Self
353    where
354        H: Handler<Args> + 'static,
355        H::Output: IntoResponse,
356    {
357        let handler_cell = std::cell::RefCell::new(Some(handler));
358        self.method_not_allowed_handler = Some(Box::new(move |req| {
359            if let Some(h) = handler_cell.borrow_mut().take() {
360                h.call(req).into_response()
361            } else {
362                Response::new(StatusCode::INTERNAL_SERVER_ERROR)
363                    .with_text("Handler already consumed")
364            }
365        }));
366        self
367    }
368
369    /// Nests another router under a path prefix.
370    ///
371    /// # Example
372    ///
373    /// ```ignore
374    /// let api_router = Router::new()
375    ///     .get("/users", list_users)
376    ///     .get("/users/:id", get_user);
377    ///
378    /// let main_router = Router::new()
379    ///     .get("/", home)
380    ///     .nest("/api", api_router);
381    /// ```
382    pub fn nest(mut self, prefix: &str, nested: Router) -> Self {
383        let prefix = prefix.trim_end_matches('/');
384
385        for route in nested.routes {
386            // Combine prefix with route pattern
387            let combined_pattern = if route.pattern.segments.is_empty() {
388                prefix.to_string()
389            } else {
390                let pattern_str: String = route
391                    .pattern
392                    .segments
393                    .iter()
394                    .map(|seg| match seg {
395                        PatternSegment::Literal(s) => format!("/{}", s),
396                        PatternSegment::Param(s) => format!("/:{}", s),
397                        PatternSegment::Wildcard => "/*".to_string(),
398                    })
399                    .collect();
400                format!("{}{}", prefix, pattern_str)
401            };
402
403            self.routes.push(Route {
404                method: route.method,
405                pattern: RoutePattern::parse(&combined_pattern),
406                handler: route.handler,
407            });
408        }
409
410        self
411    }
412
413    fn add_route<H, Args>(mut self, method: Option<Method>, pattern: &str, handler: H) -> Self
414    where
415        H: Handler<Args> + 'static,
416        H::Output: IntoResponse,
417    {
418        // We need to store the handler in a way that can be called multiple times.
419        // Since Handler::call takes self by value, we need a different approach.
420        // We'll use a Cell to allow taking ownership each time.
421        let handler_cell = std::cell::RefCell::new(Some(handler));
422
423        let boxed: BoxedHandler = Box::new(move |req| {
424            // This is a limitation - each handler can only be called once per route registration.
425            // For a real router, we'd need handlers to be Clone or use Fn instead of FnOnce.
426            // For now, we'll panic if called twice (which shouldn't happen in normal use).
427            if let Some(h) = handler_cell.borrow_mut().take() {
428                h.call(req).into_response()
429            } else {
430                // Handler was already consumed - this shouldn't happen with proper usage
431                Response::new(StatusCode::INTERNAL_SERVER_ERROR)
432                    .with_text("Handler already consumed")
433            }
434        });
435
436        self.routes.push(Route {
437            method,
438            pattern: RoutePattern::parse(pattern),
439            handler: boxed,
440        });
441
442        self
443    }
444
445    /// Handles a request and returns a response.
446    pub fn handle(&self, req: &mut Request) -> Response {
447        let method = req.method();
448        let path = req.path().to_string();
449
450        // Track if we found a matching path (for 405 vs 404)
451        let mut path_matched = false;
452
453        for route in &self.routes {
454            if let Some(params) = route.pattern.matches(&path) {
455                path_matched = true;
456
457                // Check method
458                if let Some(route_method) = route.method {
459                    if route_method != method {
460                        continue;
461                    }
462                }
463
464                // Set params for extractors
465                set_current_params(params);
466
467                // Call handler
468                return (route.handler)(req);
469            }
470        }
471
472        // No matching route found
473        if path_matched {
474            // Path matched but method didn't - 405
475            if let Some(ref handler) = self.method_not_allowed_handler {
476                return handler(req);
477            }
478            Response::new(StatusCode::METHOD_NOT_ALLOWED)
479                .with_text("Method Not Allowed")
480        } else {
481            // No path match - 404
482            if let Some(ref handler) = self.not_found_handler {
483                return handler(req);
484            }
485            Response::new(StatusCode::NOT_FOUND).with_text("Not Found")
486        }
487    }
488
489    /// Runs the router, handling the current request.
490    ///
491    /// This is the main entry point for using the router.
492    ///
493    /// # Example
494    ///
495    /// ```ignore
496    /// #[unsafe(no_mangle)]
497    /// pub extern "C" fn _start() {
498    ///     Router::new()
499    ///         .get("/", || "Hello!")
500    ///         .run();
501    /// }
502    /// ```
503    pub fn run(self) {
504        let mut request = Request::new();
505        let method = request.method();
506        let path = request.path().to_string();
507        crate::log_info!("Handling request {} {}", method, path);
508        let response = self.handle(&mut request);
509        response.send();
510    }
511}