@@ -154,9 +154,38 @@ 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+ enum RouteOn {
187+ FullPath ,
188+ Suffix ,
160189}
161190
162191impl Default for Router {
@@ -203,7 +232,16 @@ impl Router {
203232 Err ( e) => return e. into_response ( ) ,
204233 } ;
205234 let method = request. method . clone ( ) ;
206- let path = & request. path ( ) ;
235+ let path = match self . route_on {
236+ RouteOn :: FullPath => request. path ( ) ,
237+ RouteOn :: Suffix => match trailing_suffix ( & request) {
238+ Some ( path) => path,
239+ None => {
240+ eprintln ! ( "Internal error: Router configured with suffix routing but trigger route has no trailing wildcard" ) ;
241+ return responses:: internal_server_error ( ) ;
242+ }
243+ } ,
244+ } ;
207245 let RouteMatch { params, handler } = self . find ( path, method) ;
208246 handler. handle ( request, params) . await
209247 }
@@ -516,11 +554,22 @@ impl Router {
516554 self . add_async ( path, Method :: Options , handler)
517555 }
518556
519- /// Construct a new Router.
557+ /// Construct a new Router that matches on the full path .
520558 pub fn new ( ) -> Self {
521559 Router {
522560 methods_map : HashMap :: default ( ) ,
523561 any_methods : MethodRouter :: new ( ) ,
562+ route_on : RouteOn :: FullPath ,
563+ }
564+ }
565+
566+ /// Construct a new Router that matches on the trailing wildcard
567+ /// component of the route.
568+ pub fn suffix ( ) -> Self {
569+ Router {
570+ methods_map : HashMap :: default ( ) ,
571+ any_methods : MethodRouter :: new ( ) ,
572+ route_on : RouteOn :: Suffix ,
524573 }
525574 }
526575}
@@ -533,6 +582,11 @@ async fn method_not_allowed(_req: Request, _params: Params) -> Response {
533582 responses:: method_not_allowed ( )
534583}
535584
585+ fn trailing_suffix ( req : & Request ) -> Option < & str > {
586+ req. header ( "spin-path-info" )
587+ . and_then ( |path_info| path_info. as_str ( ) )
588+ }
589+
536590/// A macro to help with constructing a Router from a stream of tokens.
537591#[ macro_export]
538592macro_rules! http_router {
@@ -579,6 +633,12 @@ mod tests {
579633 Request :: new ( method, path)
580634 }
581635
636+ fn make_wildcard_request ( method : Method , path : & str , trailing : & str ) -> Request {
637+ let mut req = Request :: new ( method, path) ;
638+ req. set_header ( "spin-path-info" , trailing) ;
639+ req
640+ }
641+
582642 fn echo_param ( _req : Request , params : Params ) -> Response {
583643 match params. get ( "x" ) {
584644 Some ( path) => Response :: new ( 200 , path) ,
@@ -666,6 +726,16 @@ mod tests {
666726 assert_eq ! ( res. body, "foo" . to_owned( ) . into_bytes( ) ) ;
667727 }
668728
729+ #[ test]
730+ fn test_spin_trailing_wildcard ( ) {
731+ let mut router = Router :: suffix ( ) ;
732+ router. get ( "/:x/*" , echo_param) ;
733+
734+ let req = make_wildcard_request ( Method :: Get , "/base/baser/foo/bar" , "/foo/bar" ) ;
735+ let res = router. handle ( req) ;
736+ assert_eq ! ( res. body, "foo" . to_owned( ) . into_bytes( ) ) ;
737+ }
738+
669739 #[ test]
670740 fn test_router_display ( ) {
671741 let mut router = Router :: default ( ) ;
0 commit comments