@@ -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+ /// ```
157180pub 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
162195impl 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]
538596macro_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