@@ -154,9 +154,42 @@ pub type Params = Captures<'static, 'static>;
154
154
/// # fn handle_single_segment(req: Request, params: Params) -> anyhow::Result<Response> { todo!() }
155
155
/// # fn handle_exact(req: Request, params: Params) -> anyhow::Result<Response> { todo!() }
156
156
/// ```
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
+ /// ```
157
180
pub struct Router {
158
181
methods_map : HashMap < Method , MethodRouter < Box < dyn Handler > > > ,
159
182
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 ,
160
193
}
161
194
162
195
impl Default for Router {
@@ -203,7 +236,16 @@ impl Router {
203
236
Err ( e) => return e. into_response ( ) ,
204
237
} ;
205
238
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
+ } ;
207
249
let RouteMatch { params, handler } = self . find ( path, method) ;
208
250
handler. handle ( request, params) . await
209
251
}
@@ -516,11 +558,22 @@ impl Router {
516
558
self . add_async ( path, Method :: Options , handler)
517
559
}
518
560
519
- /// Construct a new Router.
561
+ /// Construct a new Router that matches on the full path .
520
562
pub fn new ( ) -> Self {
521
563
Router {
522
564
methods_map : HashMap :: default ( ) ,
523
565
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 ,
524
577
}
525
578
}
526
579
}
@@ -533,6 +586,11 @@ async fn method_not_allowed(_req: Request, _params: Params) -> Response {
533
586
responses:: method_not_allowed ( )
534
587
}
535
588
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
+
536
594
/// A macro to help with constructing a Router from a stream of tokens.
537
595
#[ macro_export]
538
596
macro_rules! http_router {
@@ -579,6 +637,12 @@ mod tests {
579
637
Request :: new ( method, path)
580
638
}
581
639
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
+
582
646
fn echo_param ( _req : Request , params : Params ) -> Response {
583
647
match params. get ( "x" ) {
584
648
Some ( path) => Response :: new ( 200 , path) ,
@@ -666,6 +730,16 @@ mod tests {
666
730
assert_eq ! ( res. body, "foo" . to_owned( ) . into_bytes( ) ) ;
667
731
}
668
732
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
+
669
743
#[ test]
670
744
fn test_router_display ( ) {
671
745
let mut router = Router :: default ( ) ;
0 commit comments