44
55use anyhow:: { anyhow, Result } ;
66use indexmap:: IndexMap ;
7- use std:: { collections:: HashMap , fmt} ;
7+ use std:: { borrow :: Cow , collections:: HashMap , fmt} ;
88
99use crate :: config:: HttpTriggerRouteConfig ;
1010
@@ -22,9 +22,9 @@ struct RouteHandler {
2222 /// The component ID that the route maps to.
2323 component_id : String ,
2424 /// The route, including any application base.
25- based_route : String ,
25+ based_route : Cow < ' static , str > ,
2626 /// The route, not including any application base.
27- raw_route : String ,
27+ raw_route : Cow < ' static , str > ,
2828 /// The route, including any application base and capturing information about whether it has a trailing wildcard.
2929 /// (This avoids re-parsing the route string.)
3030 parsed_based_route : ParsedRoute ,
@@ -111,8 +111,8 @@ impl Router {
111111
112112 let handler = RouteHandler {
113113 component_id : re. component_id . to_string ( ) ,
114- based_route : re. based_route ,
115- raw_route : re. raw_route . to_string ( ) ,
114+ based_route : re. based_route . into ( ) ,
115+ raw_route : re. raw_route . to_string ( ) . into ( ) ,
116116 parsed_based_route : parsed,
117117 } ;
118118
@@ -163,37 +163,24 @@ impl Router {
163163 /// If multiple components could potentially handle the same request based on their
164164 /// defined routes, components with matching exact routes take precedence followed
165165 /// by matching wildcard patterns with the longest matching prefix.
166- pub fn route ( & self , p : & str ) -> Result < RouteMatch > {
166+ pub fn route < ' path , ' router : ' path > (
167+ & ' router self ,
168+ path : & ' path str ,
169+ ) -> Result < RouteMatch < ' router , ' path > > {
167170 let best_match = self
168171 . router
169- . best_match ( p )
170- . ok_or_else ( || anyhow ! ( "Cannot match route for path {p }" ) ) ?;
172+ . best_match ( path )
173+ . ok_or_else ( || anyhow ! ( "Cannot match route for path {path }" ) ) ?;
171174
172- let route_handler = best_match. handler ( ) . clone ( ) ;
173- let named_wildcards = best_match
174- . captures ( )
175- . iter ( )
176- . map ( |( k, v) | ( k. to_owned ( ) , v. to_owned ( ) ) )
177- . collect ( ) ;
178- let trailing_wildcard = best_match. captures ( ) . wildcard ( ) . map ( |s|
179- // Backward compatibility considerations - Spin has traditionally
180- // captured trailing slashes, but routefinder does not.
181- match ( s. is_empty ( ) , p. ends_with ( '/' ) ) {
182- // route: /foo/..., path: /foo
183- ( true , false ) => s. to_owned ( ) ,
184- // route: /foo/..., path: /foo/
185- ( true , true ) => "/" . to_owned ( ) ,
186- // route: /foo/..., path: /foo/bar
187- ( false , false ) => format ! ( "/{s}" ) ,
188- // route: /foo/..., path: /foo/bar/
189- ( false , true ) => format ! ( "/{s}/" ) ,
190- }
191- ) ;
175+ let route_handler = best_match. handler ( ) ;
176+ let captures = best_match. captures ( ) ;
192177
193178 Ok ( RouteMatch {
194- route_handler,
195- named_wildcards,
196- trailing_wildcard,
179+ inner : RouteMatchKind :: Real {
180+ route_handler,
181+ captures,
182+ path,
183+ } ,
197184 } )
198185 }
199186}
@@ -235,69 +222,136 @@ impl fmt::Display for ParsedRoute {
235222}
236223
237224/// A routing match for a URL.
238- pub struct RouteMatch {
239- route_handler : RouteHandler ,
240- named_wildcards : HashMap < String , String > ,
241- trailing_wildcard : Option < String > ,
225+ pub struct RouteMatch < ' router , ' path > {
226+ inner : RouteMatchKind < ' router , ' path > ,
242227}
243228
244- impl RouteMatch {
229+ impl RouteMatch < ' _ , ' _ > {
245230 /// A synthetic match as if the given path was matched against the wildcard route.
246231 /// Used in service chaining.
247- pub fn synthetic ( component_id : & str , path : & str ) -> Self {
232+ pub fn synthetic ( component_id : String , path : String ) -> Self {
248233 Self {
249- route_handler : RouteHandler {
250- component_id : component_id. to_string ( ) ,
251- based_route : "/..." . to_string ( ) ,
252- raw_route : "/..." . to_string ( ) ,
253- parsed_based_route : ParsedRoute :: TrailingWildcard ( String :: new ( ) ) ,
234+ inner : RouteMatchKind :: Synthetic {
235+ route_handler : RouteHandler {
236+ component_id,
237+ based_route : "/..." . into ( ) ,
238+ raw_route : "/..." . into ( ) ,
239+ parsed_based_route : ParsedRoute :: TrailingWildcard ( String :: new ( ) ) ,
240+ } ,
241+ trailing_wildcard : path,
254242 } ,
255- named_wildcards : Default :: default ( ) ,
256- trailing_wildcard : Some ( path. to_string ( ) ) ,
257243 }
258244 }
259245
260246 /// The matched component.
261247 pub fn component_id ( & self ) -> & str {
262- & self . route_handler . component_id
248+ & self . inner . route_handler ( ) . component_id
263249 }
264250
265251 /// The matched route, as originally written in the manifest, combined with the base.
266252 pub fn based_route ( & self ) -> & str {
267- & self . route_handler . based_route
253+ & self . inner . route_handler ( ) . based_route
268254 }
269255
270256 /// The matched route, excluding any trailing wildcard, combined with the base.
271- pub fn based_route_or_prefix ( & self ) -> String {
272- self . route_handler
257+ pub fn based_route_or_prefix ( & self ) -> & str {
258+ self . inner
259+ . route_handler ( )
273260 . based_route
274261 . strip_suffix ( "/..." )
275- . unwrap_or ( & self . route_handler . based_route )
276- . to_string ( )
262+ . unwrap_or ( & self . inner . route_handler ( ) . based_route )
277263 }
278264
279265 /// The matched route, as originally written in the manifest.
280266 pub fn raw_route ( & self ) -> & str {
281- & self . route_handler . raw_route
267+ & self . inner . route_handler ( ) . raw_route
282268 }
283269
284270 /// The matched route, excluding any trailing wildcard.
285- pub fn raw_route_or_prefix ( & self ) -> String {
286- self . route_handler
271+ pub fn raw_route_or_prefix ( & self ) -> & str {
272+ self . inner
273+ . route_handler ( )
287274 . raw_route
288275 . strip_suffix ( "/..." )
289- . unwrap_or ( & self . route_handler . raw_route )
290- . to_string ( )
276+ . unwrap_or ( & self . inner . route_handler ( ) . raw_route )
277+ }
278+
279+ /// The named wildcards captured from the path, if any
280+ pub fn named_wildcards ( & self ) -> HashMap < & str , & str > {
281+ self . inner . named_wildcards ( )
282+ }
283+
284+ /// The trailing wildcard part of the path, if any
285+ pub fn trailing_wildcard ( & self ) -> Cow < ' _ , str > {
286+ self . inner . trailing_wildcard ( )
287+ }
288+ }
289+
290+ /// The kind of route match that was made.
291+ ///
292+ /// Can either be real based on the routefinder or synthetic based on hardcoded results.
293+ enum RouteMatchKind < ' router , ' path > {
294+ /// A synthetic match as if the given path was matched against the wildcard route.
295+ Synthetic {
296+ /// The route handler that matched the path.
297+ route_handler : RouteHandler ,
298+ /// The trailing wildcard part of the path
299+ trailing_wildcard : String ,
300+ } ,
301+ /// A real match.
302+ Real {
303+ /// The route handler that matched the path.
304+ route_handler : & ' router RouteHandler ,
305+ /// The best match for the path.
306+ captures : routefinder:: Captures < ' router , ' path > ,
307+ /// The path that was matched.
308+ path : & ' path str ,
309+ } ,
310+ }
311+
312+ impl RouteMatchKind < ' _ , ' _ > {
313+ /// The route handler that matched the path.
314+ fn route_handler ( & self ) -> & RouteHandler {
315+ match self {
316+ RouteMatchKind :: Synthetic { route_handler, .. } => route_handler,
317+ RouteMatchKind :: Real { route_handler, .. } => route_handler,
318+ }
291319 }
292320
293321 /// The named wildcards captured from the path, if any
294- pub fn named_wildcards ( & self ) -> & HashMap < String , String > {
295- & self . named_wildcards
322+ pub fn named_wildcards ( & self ) -> HashMap < & str , & str > {
323+ let Self :: Real { captures, .. } = & self else {
324+ return HashMap :: new ( ) ;
325+ } ;
326+ captures. iter ( ) . collect ( )
296327 }
297328
298329 /// The trailing wildcard part of the path, if any
299- pub fn trailing_wildcard ( & self ) -> String {
300- self . trailing_wildcard . clone ( ) . unwrap_or_default ( )
330+ pub fn trailing_wildcard ( & self ) -> Cow < ' _ , str > {
331+ let ( captures, path) = match self {
332+ // If we have a synthetic match, we already have the trailing wildcard.
333+ Self :: Synthetic {
334+ trailing_wildcard, ..
335+ } => return trailing_wildcard. into ( ) ,
336+ Self :: Real { captures, path, .. } => ( captures, path) ,
337+ } ;
338+
339+ captures
340+ . wildcard ( )
341+ . map ( |s|
342+ // Backward compatibility considerations - Spin has traditionally
343+ // captured trailing slashes, but routefinder does not.
344+ match ( s. is_empty ( ) , path. ends_with ( '/' ) ) {
345+ // route: /foo/..., path: /foo
346+ ( true , false ) => s. into ( ) ,
347+ // route: /foo/..., path: /foo/
348+ ( true , true ) => "/" . into ( ) ,
349+ // route: /foo/..., path: /foo/bar
350+ ( false , false ) => format ! ( "/{s}" ) . into ( ) ,
351+ // route: /foo/..., path: /foo/bar/
352+ ( false , true ) => format ! ( "/{s}/" ) . into ( ) ,
353+ } )
354+ . unwrap_or_default ( )
301355 }
302356}
303357
0 commit comments