4
4
5
5
use anyhow:: { anyhow, Result } ;
6
6
use indexmap:: IndexMap ;
7
- use std:: { collections:: HashMap , fmt} ;
7
+ use std:: { borrow :: Cow , collections:: HashMap , fmt} ;
8
8
9
9
use crate :: config:: HttpTriggerRouteConfig ;
10
10
@@ -22,9 +22,9 @@ struct RouteHandler {
22
22
/// The component ID that the route maps to.
23
23
component_id : String ,
24
24
/// The route, including any application base.
25
- based_route : String ,
25
+ based_route : Cow < ' static , str > ,
26
26
/// The route, not including any application base.
27
- raw_route : String ,
27
+ raw_route : Cow < ' static , str > ,
28
28
/// The route, including any application base and capturing information about whether it has a trailing wildcard.
29
29
/// (This avoids re-parsing the route string.)
30
30
parsed_based_route : ParsedRoute ,
@@ -111,8 +111,8 @@ impl Router {
111
111
112
112
let handler = RouteHandler {
113
113
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 ( ) ,
116
116
parsed_based_route : parsed,
117
117
} ;
118
118
@@ -163,37 +163,24 @@ impl Router {
163
163
/// If multiple components could potentially handle the same request based on their
164
164
/// defined routes, components with matching exact routes take precedence followed
165
165
/// 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 > > {
167
170
let best_match = self
168
171
. 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 }" ) ) ?;
171
174
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 ( ) ;
192
177
193
178
Ok ( RouteMatch {
194
- route_handler,
195
- named_wildcards,
196
- trailing_wildcard,
179
+ inner : RouteMatchKind :: Real {
180
+ route_handler,
181
+ captures,
182
+ path,
183
+ } ,
197
184
} )
198
185
}
199
186
}
@@ -235,69 +222,136 @@ impl fmt::Display for ParsedRoute {
235
222
}
236
223
237
224
/// 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 > ,
242
227
}
243
228
244
- impl RouteMatch {
229
+ impl RouteMatch < ' _ , ' _ > {
245
230
/// A synthetic match as if the given path was matched against the wildcard route.
246
231
/// Used in service chaining.
247
- pub fn synthetic ( component_id : & str , path : & str ) -> Self {
232
+ pub fn synthetic ( component_id : String , path : String ) -> Self {
248
233
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,
254
242
} ,
255
- named_wildcards : Default :: default ( ) ,
256
- trailing_wildcard : Some ( path. to_string ( ) ) ,
257
243
}
258
244
}
259
245
260
246
/// The matched component.
261
247
pub fn component_id ( & self ) -> & str {
262
- & self . route_handler . component_id
248
+ & self . inner . route_handler ( ) . component_id
263
249
}
264
250
265
251
/// The matched route, as originally written in the manifest, combined with the base.
266
252
pub fn based_route ( & self ) -> & str {
267
- & self . route_handler . based_route
253
+ & self . inner . route_handler ( ) . based_route
268
254
}
269
255
270
256
/// 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 ( )
273
260
. based_route
274
261
. strip_suffix ( "/..." )
275
- . unwrap_or ( & self . route_handler . based_route )
276
- . to_string ( )
262
+ . unwrap_or ( & self . inner . route_handler ( ) . based_route )
277
263
}
278
264
279
265
/// The matched route, as originally written in the manifest.
280
266
pub fn raw_route ( & self ) -> & str {
281
- & self . route_handler . raw_route
267
+ & self . inner . route_handler ( ) . raw_route
282
268
}
283
269
284
270
/// 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 ( )
287
274
. raw_route
288
275
. 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
+ }
291
319
}
292
320
293
321
/// 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 ( )
296
327
}
297
328
298
329
/// 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 ( )
301
355
}
302
356
}
303
357
0 commit comments