Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add catch all routes #197

Merged
merged 7 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions crates/router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ mod tests {
let fixed_route = build_route("/fixed.wasm");
let param_folder_route = build_route("/[id]/fixed.wasm");
let param_sub_route = build_route("/sub/[id].wasm");
let catch_all_sub_route = build_route("/sub/[...all].wasm");

let tests = [
(&param_route, "/a", RouteAffinity::CanManage(1)),
Expand All @@ -137,10 +138,15 @@ mod tests {
(&param_folder_route, "/a/fixed", RouteAffinity::CanManage(1)),
(&param_sub_route, "/a/b", RouteAffinity::CannotManage),
(&param_sub_route, "/sub/b", RouteAffinity::CanManage(2)),
(
&catch_all_sub_route,
"/sub/catch/all/routes",
RouteAffinity::CanManage(i32::MAX),
),
];

for t in tests {
assert_eq!(t.0.affinity(t.1), t.2);
for (route, path, route_affinity) in tests {
assert_eq!(route.affinity(path), route_affinity);
}
}

Expand All @@ -162,20 +168,23 @@ mod tests {
let fixed_route = build_route("/fixed.wasm");
let param_folder_route = build_route("/[id]/fixed.wasm");
let param_sub_route = build_route("/sub/[id].wasm");
let catch_all_sub_route = build_route("/sub/[...all].wasm");

// I'm gonna use this values for comparison as `routes` consumes
// the Route elements.
let param_path = param_route.path.clone();
let fixed_path = fixed_route.path.clone();
let param_folder_path = param_folder_route.path.clone();
let param_sub_path = param_sub_route.path.clone();
let catch_all_sub_path: String = catch_all_sub_route.path.clone();

let routes = Routes {
routes: vec![
param_route,
fixed_route,
param_folder_route,
param_sub_route,
catch_all_sub_route,
],
prefix: String::from("/"),
};
Expand All @@ -185,13 +194,14 @@ mod tests {
("/fixed", Some(fixed_path)),
("/a/fixed", Some(param_folder_path)),
("/sub/b", Some(param_sub_path)),
("/sub/catch/all/routes", Some(catch_all_sub_path)),
("/donot/exist", None),
];

for t in tests {
let route = routes.retrieve_best_route(t.0);
for (given_path, expected_path) in tests {
let route = routes.retrieve_best_route(given_path);

if let Some(path) = t.1 {
if let Some(path) = expected_path {
assert!(route.is_some());
assert_eq!(route.unwrap().path, path);
} else {
Expand Down
51 changes: 29 additions & 22 deletions crates/router/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use wws_config::Config as ProjectConfig;
use wws_worker::Worker;

lazy_static! {
static ref PARAMETER_REGEX: Regex = Regex::new(r"\[\w+\]").unwrap();
static ref DYNAMIC_ROUTE_REGEX: Regex = Regex::new(r".*\[\w+\].*").unwrap();
static ref PARAMETER_REGEX: Regex =
Regex::new(r"\[(?P<ellipsis>\.{3})?(?P<segment>\w+)\]").unwrap();
ereslibre marked this conversation as resolved.
Show resolved Hide resolved
}

/// Identify if a route can manage a certain URL and generates
Expand Down Expand Up @@ -118,54 +118,61 @@ impl Route {
/// - /a/[id].js
/// - /[id]/b.wasm
/// - /[id]/[other].wasm
/// - /[id]/[..all].wasm
///
/// We need to establish a priority. The lower of the returned number,
/// the more priority it has. This number is calculated based on the number of used
/// parameters, as fixed routes has more priority than parameted ones.
///
/// To avoid collisions like `[id]/b.wasm` vs `/a/[id].js`. Every depth level will
/// add an extra +1 to the score. So, in case of `[id]/b.wasm` vs `/a/[id].js`,
/// the /a/b path will be managed by `[id]/b.wasm`
/// the /a/b path will be managed by `/a/[id].js`
///
/// In case it cannot manage it, it will return -1
pub fn affinity(&self, url_path: &str) -> RouteAffinity {
let mut score: i32 = 0;
let mut split_path = self.path.split('/').peekable();

for (depth, portion) in url_path.split('/').enumerate() {
for (depth, segment) in url_path.split('/').enumerate() {
match split_path.next() {
Some(el) if el == portion => continue,
Some(el) if PARAMETER_REGEX.is_match(el) => {
score += depth as i32;
continue;
}
_ => return RouteAffinity::CannotManage,
None => return RouteAffinity::CannotManage,
Some(el) if el == segment => continue,
Some(el) => match PARAMETER_REGEX.captures(el) {
None => return RouteAffinity::CannotManage,
Some(caps) => match (caps.name("ellipsis"), caps.name("segment")) {
(Some(_), Some(_)) => return RouteAffinity::CanManage(i32::MAX),
ereslibre marked this conversation as resolved.
Show resolved Hide resolved
(_, Some(_)) => score += depth as i32,
_ => return RouteAffinity::CannotManage,
},
},
}
}

// I should check the other iterator to confirm is empty
if split_path.peek().is_none() {
RouteAffinity::CanManage(score)
} else {
// The split path iterator still have some entries.
RouteAffinity::CannotManage
// I should check the other iterator to confirm if it is empty
match split_path.peek() {
None => RouteAffinity::CanManage(score),
Some(_) => RouteAffinity::CannotManage,
}
}

/// Returns the given path with the actix format. For dynamic routing
/// we are using `[]` in the filenames. However, actix expects a `{}`
/// format for parameters.
pub fn actix_path(&self) -> String {
// Replace [] with {} for making the path compatible with
let mut formatted = self.path.replace('[', "{");
formatted = formatted.replace(']', "}");

formatted
PARAMETER_REGEX
.replace_all(&self.path, |caps: &regex::Captures| {
match (caps.name("ellipsis"), caps.name("segment")) {
(Some(_), Some(segment)) => format!("{{{}:.*}}", segment.as_str()),
(_, Some(segment)) => format!("{{{}}}", segment.as_str()),
_ => String::new(),
}
})
.into()
}

/// Check if the current route is dynamic
pub fn is_dynamic(&self) -> bool {
DYNAMIC_ROUTE_REGEX.is_match(&self.path)
PARAMETER_REGEX.is_match(&self.path)
ereslibre marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
Binary file added tests/data/params/sub/[...all].wasm
Binary file not shown.