Skip to content

Commit 71c9aa4

Browse files
authored
Merge pull request #66 from itowlson/route-on-trailing-wildcard
Routing modes
2 parents b1aa715 + 5bd986f commit 71c9aa4

File tree

1 file changed

+76
-2
lines changed

1 file changed

+76
-2
lines changed

src/http/router.rs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,42 @@ pub type Params = Captures<'static, 'static>;
154154
/// # fn handle_single_segment(req: Request, params: Params) -> anyhow::Result<Response> { todo!() }
155155
/// # fn handle_exact(req: Request, params: Params) -> anyhow::Result<Response> { todo!() }
156156
/// ```
157+
///
158+
/// Route based on the trailing segment of a Spin wildcard route, instead of on the full path
159+
///
160+
/// ```no_run
161+
/// // spin.toml
162+
/// //
163+
/// // [[trigger.http]]
164+
/// // route = "/shop/..."
165+
///
166+
/// // component
167+
/// # use spin_sdk::http::{IntoResponse, Params, Request, Response, Router};
168+
/// fn handle_route(req: Request) -> Response {
169+
/// let mut router = Router::suffix();
170+
/// router.any("/users/*", handle_users);
171+
/// router.any("/products/*", handle_products);
172+
/// router.handle(req)
173+
/// }
174+
///
175+
/// // '/shop/users/1' is routed to `handle_users`
176+
/// // '/shop/products/1' is routed to `handle_products`
177+
/// # fn handle_users(req: Request, params: Params) -> anyhow::Result<Response> { todo!() }
178+
/// # fn handle_products(req: Request, params: Params) -> anyhow::Result<Response> { todo!() }
179+
/// ```
157180
pub struct Router {
158181
methods_map: HashMap<Method, MethodRouter<Box<dyn Handler>>>,
159182
any_methods: MethodRouter<Box<dyn Handler>>,
183+
route_on: RouteOn,
184+
}
185+
186+
/// Describes what part of the path the Router will route on.
187+
enum RouteOn {
188+
/// The router will route on the full path.
189+
FullPath,
190+
/// The router expects the component to be handling a route with a trailing wildcard
191+
/// (e.g. `route = /shop/...`), and will route on the trailing segment.
192+
Suffix,
160193
}
161194

162195
impl Default for Router {
@@ -203,7 +236,16 @@ impl Router {
203236
Err(e) => return e.into_response(),
204237
};
205238
let method = request.method.clone();
206-
let path = &request.path();
239+
let path = match self.route_on {
240+
RouteOn::FullPath => request.path(),
241+
RouteOn::Suffix => match trailing_suffix(&request) {
242+
Some(path) => path,
243+
None => {
244+
eprintln!("Internal error: Router configured with suffix routing but trigger route has no trailing wildcard");
245+
return responses::internal_server_error();
246+
}
247+
},
248+
};
207249
let RouteMatch { params, handler } = self.find(path, method);
208250
handler.handle(request, params).await
209251
}
@@ -516,11 +558,22 @@ impl Router {
516558
self.add_async(path, Method::Options, handler)
517559
}
518560

519-
/// Construct a new Router.
561+
/// Construct a new Router that matches on the full path.
520562
pub fn new() -> Self {
521563
Router {
522564
methods_map: HashMap::default(),
523565
any_methods: MethodRouter::new(),
566+
route_on: RouteOn::FullPath,
567+
}
568+
}
569+
570+
/// Construct a new Router that matches on the trailing wildcard
571+
/// component of the route.
572+
pub fn suffix() -> Self {
573+
Router {
574+
methods_map: HashMap::default(),
575+
any_methods: MethodRouter::new(),
576+
route_on: RouteOn::Suffix,
524577
}
525578
}
526579
}
@@ -533,6 +586,11 @@ async fn method_not_allowed(_req: Request, _params: Params) -> Response {
533586
responses::method_not_allowed()
534587
}
535588

589+
fn trailing_suffix(req: &Request) -> Option<&str> {
590+
req.header("spin-path-info")
591+
.and_then(|path_info| path_info.as_str())
592+
}
593+
536594
/// A macro to help with constructing a Router from a stream of tokens.
537595
#[macro_export]
538596
macro_rules! http_router {
@@ -579,6 +637,12 @@ mod tests {
579637
Request::new(method, path)
580638
}
581639

640+
fn make_wildcard_request(method: Method, path: &str, trailing: &str) -> Request {
641+
let mut req = Request::new(method, path);
642+
req.set_header("spin-path-info", trailing);
643+
req
644+
}
645+
582646
fn echo_param(_req: Request, params: Params) -> Response {
583647
match params.get("x") {
584648
Some(path) => Response::new(200, path),
@@ -666,6 +730,16 @@ mod tests {
666730
assert_eq!(res.body, "foo".to_owned().into_bytes());
667731
}
668732

733+
#[test]
734+
fn test_spin_trailing_wildcard() {
735+
let mut router = Router::suffix();
736+
router.get("/:x/*", echo_param);
737+
738+
let req = make_wildcard_request(Method::Get, "/base/baser/foo/bar", "/foo/bar");
739+
let res = router.handle(req);
740+
assert_eq!(res.body, "foo".to_owned().into_bytes());
741+
}
742+
669743
#[test]
670744
fn test_router_display() {
671745
let mut router = Router::default();

0 commit comments

Comments
 (0)