worker_router/
lib.rs

1//! HTTP router for Cloudflare Workers
2//!
3//! Example using the [`worker`]:
4//! ```rust
5//! struct ServerState {}
6//!
7//! async fn get_hello(_req: Request, _state: Arc<ServerState>) -> Result<Response> {
8//!   ResponseBuilder::new().ok("hello")
9//! }
10//!
11//! #[event(fetch)]
12//! async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
13//!   let state = Arc::new(ServerState {});
14//!   let router = router::Router::new_with_state(state).get(router::path("/hello")?, get_hello);
15//!
16//!   router.run(req).await
17//! }
18//! ```
19//!
20//! [`worker`]: https://crates.io/crates/worker
21use std::future::Future;
22use std::pin::Pin;
23use std::sync::Arc;
24use urlpattern::{UrlPattern, UrlPatternInit, UrlPatternMatchInput};
25use worker::*;
26
27/// Route pattern
28pub struct Pattern(urlpattern::UrlPattern);
29
30/// Construct a route pattern using a URL path
31/// Examples:
32/// ```rust
33/// path("/hello")?;
34/// path("/users/:id")?;
35/// ```
36pub fn path(v: &str) -> Result<Pattern> {
37    let init = UrlPatternInit {
38        pathname: Some(v.to_owned()),
39        ..Default::default()
40    };
41
42    let pattern = <UrlPattern>::parse(init, Default::default())
43        .map_err(|err| Error::RustError(format!("failed to parse route pattern: {err}")))?;
44    Ok(Pattern(pattern))
45}
46
47type Handler<State> = Box<dyn Fn(Request, Arc<State>) -> ResponseFuture + 'static>;
48pub type ResponseFuture = Pin<Box<dyn Future<Output = Result<Response>> + 'static>>;
49
50struct Route<State> {
51    pattern: Pattern,
52    handler: Handler<State>,
53    method: Method,
54}
55
56/// HTTP router
57pub struct Router<State> {
58    state: Arc<State>,
59    routes: Vec<Route<State>>,
60}
61
62macro_rules! insert_method {
63    ($name:ident, $method:expr) => {
64        /// Register a new request handler for the HTTP method.
65        ///
66        /// The request handler has the following type:
67        /// ```rust
68        /// async fn handler(_req: worker::Request, _state: Arc<State>) -> Result<worker::Response>;
69        /// ```
70        pub fn $name<HandlerFn, Res>(self, pattern: Pattern, handler: HandlerFn) -> Self
71        where
72            HandlerFn: Fn(Request, Arc<State>) -> Res + 'static,
73            Res: Future<Output = Result<Response>> + 'static,
74        {
75            self.insert($method, pattern, handler)
76        }
77    };
78}
79
80impl<State> Router<State> {
81    /// Create a new router with a `State`.
82    /// The state will be passed in every request handler.
83    pub fn new_with_state(state: Arc<State>) -> Self {
84        Router {
85            routes: vec![],
86            state,
87        }
88    }
89
90    fn insert<HandlerFn, Res>(
91        mut self,
92        method: Method,
93        pattern: Pattern,
94        handler: HandlerFn,
95    ) -> Self
96    where
97        HandlerFn: Fn(Request, Arc<State>) -> Res + 'static,
98        Res: Future<Output = Result<Response>> + 'static,
99    {
100        self.routes.push(Route {
101            method,
102            pattern,
103            handler: Box::new(move |req, state| Box::pin(handler(req, state))),
104        });
105        self
106    }
107
108    insert_method!(head, Method::Head);
109    insert_method!(get, Method::Get);
110    insert_method!(post, Method::Post);
111    insert_method!(put, Method::Put);
112    insert_method!(patch, Method::Patch);
113    insert_method!(delete, Method::Delete);
114    insert_method!(options, Method::Options);
115    insert_method!(connect, Method::Connect);
116    insert_method!(trace, Method::Trace);
117
118    pub async fn run(&self, req: Request) -> Result<Response> {
119        let url = req.url()?;
120
121        for route in &self.routes {
122            if route.method != req.method() {
123                continue;
124            }
125
126            if let Some(_res) = route
127                .pattern
128                .0
129                .exec(UrlPatternMatchInput::Url(url.clone()))
130                .unwrap()
131            {
132                return (route.handler)(req, Arc::clone(&self.state)).await;
133            }
134        }
135
136        ResponseBuilder::new().error("page not found", 404)
137    }
138}