Skip to main content

goose_http/routing/
mod.rs

1//! Request routing abstractions.
2//!
3//! This module exposes traits and builders for plugging application logic into
4//! the server. It now includes a fluent router builder focused on ergonomics.
5
6use std::collections::{BTreeSet, HashMap};
7use std::sync::Arc;
8
9use crate::{
10    common::{Method, StatusCode},
11    headers::header_keys,
12    request::Request,
13    response::Response,
14};
15
16/// Trait implemented by application handlers.
17pub trait Handler: Send + Sync + 'static {
18    /// Handle a request and produce a response. Async support will be added
19    /// later using Tokio primitives.
20    fn handle(&self, request: Request) -> Response;
21}
22
23impl<F> Handler for F
24where
25    F: Fn(Request) -> Response + Send + Sync + 'static,
26{
27    fn handle(&self, request: Request) -> Response {
28        (self)(request)
29    }
30}
31
32type SharedHandler = Arc<dyn Handler>;
33
34/// Builder for constructing a [`Router`] instance with fluent method helpers.
35pub struct RouterBuilder {
36    routes: HashMap<String, RouteEntry>,
37    auto_head: bool,
38}
39
40impl Default for RouterBuilder {
41    fn default() -> Self {
42        Self {
43            routes: HashMap::new(),
44            auto_head: true,
45        }
46    }
47}
48
49impl RouterBuilder {
50    /// Create an empty router builder.
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Configure whether HEAD requests should automatically reuse GET handlers.
56    pub fn auto_head(mut self, enabled: bool) -> Self {
57        self.auto_head = enabled;
58        self
59    }
60
61    /// Register a handler for an arbitrary method token.
62    pub fn route<H>(mut self, method: Method, path: impl Into<String>, handler: H) -> Self
63    where
64        H: Handler,
65    {
66        let shared: SharedHandler = Arc::new(handler);
67        self.insert_route(method, path.into(), shared);
68        self
69    }
70
71    /// Register a GET handler.
72    pub fn get<H>(self, path: impl Into<String>, handler: H) -> Self
73    where
74        H: Handler,
75    {
76        self.route(Method::Get, path, handler)
77    }
78
79    /// Register a HEAD handler.
80    pub fn head<H>(self, path: impl Into<String>, handler: H) -> Self
81    where
82        H: Handler,
83    {
84        self.route(Method::Head, path, handler)
85    }
86
87    /// Register a POST handler.
88    pub fn post<H>(self, path: impl Into<String>, handler: H) -> Self
89    where
90        H: Handler,
91    {
92        self.route(Method::Post, path, handler)
93    }
94
95    /// Register a PUT handler.
96    pub fn put<H>(self, path: impl Into<String>, handler: H) -> Self
97    where
98        H: Handler,
99    {
100        self.route(Method::Put, path, handler)
101    }
102
103    /// Register a DELETE handler.
104    pub fn delete<H>(self, path: impl Into<String>, handler: H) -> Self
105    where
106        H: Handler,
107    {
108        self.route(Method::Delete, path, handler)
109    }
110
111    /// Register an OPTIONS handler.
112    pub fn options<H>(self, path: impl Into<String>, handler: H) -> Self
113    where
114        H: Handler,
115    {
116        self.route(Method::Options, path, handler)
117    }
118
119    /// Register a TRACE handler.
120    pub fn trace<H>(self, path: impl Into<String>, handler: H) -> Self
121    where
122        H: Handler,
123    {
124        self.route(Method::Trace, path, handler)
125    }
126
127    /// Register a PATCH handler.
128    pub fn patch<H>(self, path: impl Into<String>, handler: H) -> Self
129    where
130        H: Handler,
131    {
132        self.route(Method::Patch, path, handler)
133    }
134
135    /// Finalise the builder into a [`Router`].
136    pub fn build(self) -> Router {
137        Router {
138            routes: self.routes,
139            auto_head: self.auto_head,
140        }
141    }
142
143    fn insert_route(&mut self, method: Method, path: String, handler: SharedHandler) {
144        let entry = self.routes.entry(path).or_insert_with(RouteEntry::default);
145        entry.insert(method, handler);
146    }
147}
148
149/// Construct a fresh [`RouterBuilder`] without needing to import the type.
150pub fn router() -> RouterBuilder {
151    RouterBuilder::default()
152}
153
154/// Router that dispatches requests to registered handlers by method and path.
155pub struct Router {
156    routes: HashMap<String, RouteEntry>,
157    auto_head: bool,
158}
159
160impl Handler for Router {
161    fn handle(&self, request: Request) -> Response {
162        let method_token = request.method().as_str().to_owned();
163        let target_key = target_key(&request);
164
165        if let Some(route) = self.routes.get(&target_key) {
166            match route.resolve(&method_token, self.auto_head) {
167                RouteMatch::Matched {
168                    handler,
169                    head_fallback,
170                } => {
171                    let mut response = handler.handle(request);
172                    if head_fallback {
173                        response.strip_body_for_head();
174                    }
175                    response
176                }
177                RouteMatch::MethodNotAllowed { allow } => {
178                    if matches!(request.method(), Method::Extension(_)) {
179                        not_implemented()
180                    } else {
181                        method_not_allowed(allow)
182                    }
183                }
184            }
185        } else if matches!(request.method(), Method::Extension(_)) {
186            not_implemented()
187        } else {
188            not_found()
189        }
190    }
191}
192
193/// Simple router that always returns a 501 placeholder.
194pub struct DefaultRouter;
195
196impl Handler for DefaultRouter {
197    fn handle(&self, _request: Request) -> Response {
198        Response::new(StatusCode::NOT_IMPLEMENTED)
199    }
200}
201
202#[derive(Default)]
203struct RouteEntry {
204    handlers: HashMap<String, SharedHandler>,
205    methods: BTreeSet<String>,
206}
207
208impl RouteEntry {
209    fn insert(&mut self, method: Method, handler: SharedHandler) {
210        let token = method.as_str().to_owned();
211        self.handlers.insert(token.clone(), handler);
212        self.methods.insert(token);
213    }
214
215    fn resolve(&self, method: &str, auto_head: bool) -> RouteMatch {
216        if let Some(handler) = self.handlers.get(method) {
217            return RouteMatch::matched(handler, false);
218        }
219
220        if auto_head && method.eq_ignore_ascii_case("HEAD") {
221            if let Some(handler) = self.handlers.get("GET") {
222                return RouteMatch::matched(handler, true);
223            }
224        }
225
226        RouteMatch::method_not_allowed(self.allow_header(auto_head))
227    }
228
229    fn allow_header(&self, auto_head: bool) -> String {
230        let mut allowed = self.methods.clone();
231        if auto_head && allowed.contains("GET") {
232            allowed.insert(String::from("HEAD"));
233        }
234        allowed.into_iter().collect::<Vec<_>>().join(", ")
235    }
236}
237
238enum RouteMatch {
239    Matched {
240        handler: SharedHandler,
241        head_fallback: bool,
242    },
243    MethodNotAllowed {
244        allow: String,
245    },
246}
247
248impl RouteMatch {
249    fn matched(handler: &SharedHandler, head_fallback: bool) -> Self {
250        RouteMatch::Matched {
251            handler: Arc::clone(handler),
252            head_fallback,
253        }
254    }
255
256    fn method_not_allowed(allow: String) -> Self {
257        RouteMatch::MethodNotAllowed { allow }
258    }
259}
260
261fn target_key(request: &Request) -> String {
262    match request.target() {
263        crate::request::RequestTarget::Origin(path)
264        | crate::request::RequestTarget::Absolute(path)
265        | crate::request::RequestTarget::Authority(path) => path.clone(),
266        crate::request::RequestTarget::Asterisk => String::from("*"),
267    }
268}
269
270fn not_found() -> Response {
271    Response::new(StatusCode::NOT_FOUND)
272}
273
274fn method_not_allowed(allow: String) -> Response {
275    let mut response = Response::new(StatusCode::METHOD_NOT_ALLOWED);
276    response.headers_mut().insert(header_keys::ALLOW, allow);
277    response
278}
279
280fn not_implemented() -> Response {
281    Response::new(StatusCode::NOT_IMPLEMENTED)
282}