|
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 |
|
@@ -163,37 +163,21 @@ 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 | + p: &'path str, |
| 169 | + ) -> Result<RouteMatch<'router, 'path>> { |
167 | 170 | let best_match = self
|
168 | 171 | .router
|
169 | 172 | .best_match(p)
|
170 | 173 | .ok_or_else(|| anyhow!("Cannot match route for path {p}"))?;
|
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(); |
192 | 176 |
|
193 | 177 | Ok(RouteMatch {
|
194 |
| - route_handler, |
195 |
| - named_wildcards, |
196 |
| - trailing_wildcard, |
| 178 | + route_handler: Cow::Borrowed(route_handler), |
| 179 | + best_match: Some((p, best_match)), |
| 180 | + trailing_wildcard: None, |
197 | 181 | })
|
198 | 182 | }
|
199 | 183 | }
|
@@ -235,25 +219,28 @@ impl fmt::Display for ParsedRoute {
|
235 | 219 | }
|
236 | 220 |
|
237 | 221 | /// A routing match for a URL.
|
238 |
| -pub struct RouteMatch { |
239 |
| - route_handler: RouteHandler, |
240 |
| - named_wildcards: HashMap<String, String>, |
| 222 | +pub struct RouteMatch<'router, 'path> { |
| 223 | + /// The route handler that matched the path. |
| 224 | + route_handler: Cow<'router, RouteHandler>, |
| 225 | + /// An optional best match for the path. |
| 226 | + best_match: Option<(&'path str, routefinder::Match<'router, 'path, RouteHandler>)>, |
| 227 | + /// An optional trailing wildcard part of the path used by the synthetic match. |
241 | 228 | trailing_wildcard: Option<String>,
|
242 | 229 | }
|
243 | 230 |
|
244 |
| -impl RouteMatch { |
| 231 | +impl<'router, 'path> RouteMatch<'router, 'path> { |
245 | 232 | /// A synthetic match as if the given path was matched against the wildcard route.
|
246 | 233 | /// Used in service chaining.
|
247 |
| - pub fn synthetic(component_id: &str, path: &str) -> Self { |
| 234 | + pub fn synthetic(component_id: String, path: String) -> Self { |
248 | 235 | Self {
|
249 |
| - route_handler: RouteHandler { |
250 |
| - component_id: component_id.to_string(), |
| 236 | + route_handler: Cow::Owned(RouteHandler { |
| 237 | + component_id, |
251 | 238 | based_route: "/...".to_string(),
|
252 | 239 | raw_route: "/...".to_string(),
|
253 | 240 | parsed_based_route: ParsedRoute::TrailingWildcard(String::new()),
|
254 |
| - }, |
255 |
| - named_wildcards: Default::default(), |
256 |
| - trailing_wildcard: Some(path.to_string()), |
| 241 | + }), |
| 242 | + best_match: None, |
| 243 | + trailing_wildcard: Some(path), |
257 | 244 | }
|
258 | 245 | }
|
259 | 246 |
|
@@ -291,13 +278,45 @@ impl RouteMatch {
|
291 | 278 | }
|
292 | 279 |
|
293 | 280 | /// The named wildcards captured from the path, if any
|
294 |
| - pub fn named_wildcards(&self) -> &HashMap<String, String> { |
295 |
| - &self.named_wildcards |
| 281 | + pub fn named_wildcards(&self) -> HashMap<String, String> { |
| 282 | + let Some((_, best_match)) = &self.best_match else { |
| 283 | + return HashMap::new(); |
| 284 | + }; |
| 285 | + best_match |
| 286 | + .captures() |
| 287 | + .iter() |
| 288 | + .map(|(k, v)| (k.to_owned(), v.to_owned())) |
| 289 | + .collect() |
296 | 290 | }
|
297 | 291 |
|
298 | 292 | /// The trailing wildcard part of the path, if any
|
299 | 293 | pub fn trailing_wildcard(&self) -> String {
|
300 |
| - self.trailing_wildcard.clone().unwrap_or_default() |
| 294 | + // If we have a trailing wildcard, return it. |
| 295 | + if let Some(wildcard) = &self.trailing_wildcard { |
| 296 | + return wildcard.clone(); |
| 297 | + }; |
| 298 | + |
| 299 | + // Otherwise, we need to extract it from the best match if we have it. |
| 300 | + let Some((path, best_match)) = &self.best_match else { |
| 301 | + return String::new(); |
| 302 | + }; |
| 303 | + best_match |
| 304 | + .captures() |
| 305 | + .wildcard() |
| 306 | + .map(|s| |
| 307 | + // Backward compatibility considerations - Spin has traditionally |
| 308 | + // captured trailing slashes, but routefinder does not. |
| 309 | + match (s.is_empty(), path.ends_with('/')) { |
| 310 | + // route: /foo/..., path: /foo |
| 311 | + (true, false) => s.to_owned(), |
| 312 | + // route: /foo/..., path: /foo/ |
| 313 | + (true, true) => "/".to_owned(), |
| 314 | + // route: /foo/..., path: /foo/bar |
| 315 | + (false, false) => format!("/{s}"), |
| 316 | + // route: /foo/..., path: /foo/bar/ |
| 317 | + (false, true) => format!("/{s}/"), |
| 318 | + }) |
| 319 | + .unwrap_or_default() |
301 | 320 | }
|
302 | 321 | }
|
303 | 322 |
|
|
0 commit comments