From 8cdfa445d6629ffef4cb84967ff7017654045bc2 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 2 Jul 2025 18:36:06 -0600 Subject: chore: add vendor directory --- vendor/matchit/tests/insert.rs | 243 ++++++++++ vendor/matchit/tests/match.rs | 1047 ++++++++++++++++++++++++++++++++++++++++ vendor/matchit/tests/remove.rs | 265 ++++++++++ 3 files changed, 1555 insertions(+) create mode 100644 vendor/matchit/tests/insert.rs create mode 100644 vendor/matchit/tests/match.rs create mode 100644 vendor/matchit/tests/remove.rs (limited to 'vendor/matchit/tests') diff --git a/vendor/matchit/tests/insert.rs b/vendor/matchit/tests/insert.rs new file mode 100644 index 00000000..5513c1c6 --- /dev/null +++ b/vendor/matchit/tests/insert.rs @@ -0,0 +1,243 @@ +use matchit::{InsertError, Router}; + +struct InsertTest(Vec<(&'static str, Result<(), InsertError>)>); + +impl InsertTest { + fn run(self) { + let mut router = Router::new(); + for (route, expected) in self.0 { + let got = router.insert(route, route.to_owned()); + assert_eq!(got, expected, "{route}"); + } + } +} + +fn conflict(with: &'static str) -> InsertError { + InsertError::Conflict { with: with.into() } +} + +#[test] +fn wildcard_conflict() { + InsertTest(vec![ + ("/cmd/{tool}/{sub}", Ok(())), + ("/cmd/vet", Ok(())), + ("/foo/bar", Ok(())), + ("/foo/{name}", Ok(())), + ("/foo/{names}", Err(conflict("/foo/{name}"))), + ("/cmd/{*path}", Err(conflict("/cmd/{tool}/{sub}"))), + ("/cmd/{xxx}/names", Ok(())), + ("/cmd/{tool}/{xxx}/foo", Ok(())), + ("/src/{*filepath}", Ok(())), + ("/src/{file}", Err(conflict("/src/{*filepath}"))), + ("/src/static.json", Ok(())), + ("/src/$filepathx", Ok(())), + ("/src/", Ok(())), + ("/src/foo/bar", Ok(())), + ("/src1/", Ok(())), + ("/src1/{*filepath}", Ok(())), + ("/src2{*filepath}", Ok(())), + ("/src2/{*filepath}", Ok(())), + ("/src2/", Ok(())), + ("/src2", Ok(())), + ("/src3", Ok(())), + ("/src3/{*filepath}", Ok(())), + ("/search/{query}", Ok(())), + ("/search/valid", Ok(())), + ("/user_{name}", Ok(())), + ("/user_x", Ok(())), + ("/user_{bar}", Err(conflict("/user_{name}"))), + ("/id{id}", Ok(())), + ("/id/{id}", Ok(())), + ]) + .run() +} + +#[test] +fn invalid_catchall() { + InsertTest(vec![ + ("/non-leading-{*catchall}", Ok(())), + ("/foo/bar{*catchall}", Ok(())), + ("/src/{*filepath}/x", Err(InsertError::InvalidCatchAll)), + ("/src2/", Ok(())), + ("/src2/{*filepath}/x", Err(InsertError::InvalidCatchAll)), + ]) + .run() +} + +#[test] +fn catchall_root_conflict() { + InsertTest(vec![("/", Ok(())), ("/{*filepath}", Ok(()))]).run() +} + +#[test] +fn child_conflict() { + InsertTest(vec![ + ("/cmd/vet", Ok(())), + ("/cmd/{tool}", Ok(())), + ("/cmd/{tool}/{sub}", Ok(())), + ("/cmd/{tool}/misc", Ok(())), + ("/cmd/{tool}/{bad}", Err(conflict("/cmd/{tool}/{sub}"))), + ("/src/AUTHORS", Ok(())), + ("/src/{*filepath}", Ok(())), + ("/user_x", Ok(())), + ("/user_{name}", Ok(())), + ("/id/{id}", Ok(())), + ("/id{id}", Ok(())), + ("/{id}", Ok(())), + ("/{*filepath}", Err(conflict("/{id}"))), + ]) + .run() +} + +#[test] +fn duplicates() { + InsertTest(vec![ + ("/", Ok(())), + ("/", Err(conflict("/"))), + ("/doc/", Ok(())), + ("/doc/", Err(conflict("/doc/"))), + ("/src/{*filepath}", Ok(())), + ("/src/{*filepath}", Err(conflict("/src/{*filepath}"))), + ("/search/{query}", Ok(())), + ("/search/{query}", Err(conflict("/search/{query}"))), + ("/user_{name}", Ok(())), + ("/user_{name}", Err(conflict("/user_{name}"))), + ]) + .run() +} + +#[test] +fn unnamed_param() { + InsertTest(vec![ + ("/{}", Err(InsertError::InvalidParam)), + ("/user{}/", Err(InsertError::InvalidParam)), + ("/cmd/{}/", Err(InsertError::InvalidParam)), + ("/src/{*}", Err(InsertError::InvalidParam)), + ]) + .run() +} + +#[test] +fn double_params() { + InsertTest(vec![ + ("/{foo}{bar}", Err(InsertError::InvalidParamSegment)), + ("/{foo}{bar}/", Err(InsertError::InvalidParamSegment)), + ("/{foo}{{*bar}/", Err(InsertError::InvalidParamSegment)), + ]) + .run() +} + +#[test] +fn normalized_conflict() { + InsertTest(vec![ + ("/x/{foo}/bar", Ok(())), + ("/x/{bar}/bar", Err(conflict("/x/{foo}/bar"))), + ("/{y}/bar/baz", Ok(())), + ("/{y}/baz/baz", Ok(())), + ("/{z}/bar/bat", Ok(())), + ("/{z}/bar/baz", Err(conflict("/{y}/bar/baz"))), + ]) + .run() +} + +#[test] +fn more_conflicts() { + InsertTest(vec![ + ("/con{tact}", Ok(())), + ("/who/are/{*you}", Ok(())), + ("/who/foo/hello", Ok(())), + ("/whose/{users}/{name}", Ok(())), + ("/who/are/foo", Ok(())), + ("/who/are/foo/bar", Ok(())), + ("/con{nection}", Err(conflict("/con{tact}"))), + ( + "/whose/{users}/{user}", + Err(conflict("/whose/{users}/{name}")), + ), + ]) + .run() +} + +#[test] +fn catchall_static_overlap() { + InsertTest(vec![ + ("/bar", Ok(())), + ("/bar/", Ok(())), + ("/bar/{*foo}", Ok(())), + ]) + .run(); + + InsertTest(vec![ + ("/foo", Ok(())), + ("/{*bar}", Ok(())), + ("/bar", Ok(())), + ("/baz", Ok(())), + ("/baz/{split}", Ok(())), + ("/", Ok(())), + ("/{*bar}", Err(conflict("/{*bar}"))), + ("/{*zzz}", Err(conflict("/{*bar}"))), + ("/{xxx}", Err(conflict("/{*bar}"))), + ]) + .run(); + + InsertTest(vec![ + ("/{*bar}", Ok(())), + ("/bar", Ok(())), + ("/bar/x", Ok(())), + ("/bar_{x}", Ok(())), + ("/bar_{x}", Err(conflict("/bar_{x}"))), + ("/bar_{x}/y", Ok(())), + ("/bar/{x}", Ok(())), + ]) + .run(); +} + +#[test] +fn duplicate_conflict() { + InsertTest(vec![ + ("/hey", Ok(())), + ("/hey/users", Ok(())), + ("/hey/user", Ok(())), + ("/hey/user", Err(conflict("/hey/user"))), + ]) + .run() +} + +#[test] +fn invalid_param() { + InsertTest(vec![ + ("{", Err(InsertError::InvalidParam)), + ("}", Err(InsertError::InvalidParam)), + ("x{y", Err(InsertError::InvalidParam)), + ("x}", Err(InsertError::InvalidParam)), + ("/{foo}s", Err(InsertError::InvalidParamSegment)), + ]) + .run(); +} + +#[test] +fn escaped_param() { + InsertTest(vec![ + ("{{", Ok(())), + ("}}", Ok(())), + ("xx}}", Ok(())), + ("}}yy", Ok(())), + ("}}yy{{}}", Ok(())), + ("}}yy{{}}{{}}y{{", Ok(())), + ("}}yy{{}}{{}}y{{", Err(conflict("}yy{}{}y{"))), + ("/{{yy", Ok(())), + ("/{yy}", Ok(())), + ("/foo", Ok(())), + ("/foo/{{", Ok(())), + ("/foo/{{/{x}", Ok(())), + ("/foo/{ba{{r}", Ok(())), + ("/bar/{ba}}r}", Ok(())), + ("/xxx/{x{{}}y}", Ok(())), + ]) + .run() +} + +#[test] +fn bare_catchall() { + InsertTest(vec![("{*foo}", Ok(())), ("foo/{*bar}", Ok(()))]).run() +} diff --git a/vendor/matchit/tests/match.rs b/vendor/matchit/tests/match.rs new file mode 100644 index 00000000..90130fbc --- /dev/null +++ b/vendor/matchit/tests/match.rs @@ -0,0 +1,1047 @@ +use matchit::{MatchError, Router}; + +// https://github.com/ibraheemdev/matchit/issues/22 +#[test] +fn partial_overlap() { + let mut x = Router::new(); + x.insert("/foo_bar", "Welcome!").unwrap(); + x.insert("/foo/bar", "Welcome!").unwrap(); + assert_eq!(x.at("/foo/").unwrap_err(), MatchError::NotFound); + + let mut x = Router::new(); + x.insert("/foo", "Welcome!").unwrap(); + x.insert("/foo/bar", "Welcome!").unwrap(); + assert_eq!(x.at("/foo/").unwrap_err(), MatchError::NotFound); +} + +// https://github.com/ibraheemdev/matchit/issues/31 +#[test] +fn wildcard_overlap() { + let mut router = Router::new(); + router.insert("/path/foo", "foo").unwrap(); + router.insert("/path/{*rest}", "wildcard").unwrap(); + + assert_eq!(router.at("/path/foo").map(|m| *m.value), Ok("foo")); + assert_eq!(router.at("/path/bar").map(|m| *m.value), Ok("wildcard")); + assert_eq!(router.at("/path/foo/").map(|m| *m.value), Ok("wildcard")); + + let mut router = Router::new(); + router.insert("/path/foo/{arg}", "foo").unwrap(); + router.insert("/path/{*rest}", "wildcard").unwrap(); + + assert_eq!(router.at("/path/foo/myarg").map(|m| *m.value), Ok("foo")); + assert_eq!( + router.at("/path/foo/myarg/").map(|m| *m.value), + Ok("wildcard") + ); + assert_eq!( + router.at("/path/foo/myarg/bar/baz").map(|m| *m.value), + Ok("wildcard") + ); +} + +// https://github.com/ibraheemdev/matchit/issues/12 +#[test] +fn overlapping_param_backtracking() { + let mut matcher = Router::new(); + + matcher.insert("/{object}/{id}", "object with id").unwrap(); + matcher + .insert("/secret/{id}/path", "secret with id and path") + .unwrap(); + + let matched = matcher.at("/secret/978/path").unwrap(); + assert_eq!(matched.params.get("id"), Some("978")); + + let matched = matcher.at("/something/978").unwrap(); + assert_eq!(matched.params.get("id"), Some("978")); + assert_eq!(matched.params.get("object"), Some("something")); + + let matched = matcher.at("/secret/978").unwrap(); + assert_eq!(matched.params.get("id"), Some("978")); +} + +struct MatchTest { + routes: Vec<&'static str>, + matches: Vec<( + &'static str, + &'static str, + Result, ()>, + )>, +} + +impl MatchTest { + fn run(self) { + let mut router = Router::new(); + + for route in self.routes { + assert_eq!(router.insert(route, route.to_owned()), Ok(()), "{route}"); + } + + router.check_priorities().unwrap(); + + for (path, route, params) in self.matches { + match router.at(path) { + Ok(x) => { + assert_eq!(x.value, route); + + let got = x.params.iter().collect::>(); + assert_eq!(params.unwrap(), got); + + router.at_mut(path).unwrap().value.push_str("Z"); + assert!(router.at(path).unwrap().value.contains("Z")); + router.at_mut(path).unwrap().value.pop(); + } + Err(_) => params.unwrap_err(), + } + } + } +} + +macro_rules! p { + ($($k:expr => $v:expr),* $(,)?) => { + Ok(vec![$(($k, $v)),*]) + }; +} + +// https://github.com/ibraheemdev/matchit/issues/42 +#[test] +fn bare_catchall() { + MatchTest { + routes: vec!["{*foo}", "foo/{*bar}"], + matches: vec![ + ("x/y", "{*foo}", p! { "foo" => "x/y" }), + ("/x/y", "{*foo}", p! { "foo" => "/x/y" }), + ("/foo/x/y", "{*foo}", p! { "foo" => "/foo/x/y" }), + ("foo/x/y", "foo/{*bar}", p! { "bar" => "x/y" }), + ], + } + .run() +} + +#[test] +fn normalized() { + MatchTest { + routes: vec![ + "/x/{foo}/bar", + "/x/{bar}/baz", + "/{foo}/{baz}/bax", + "/{foo}/{bar}/baz", + "/{fod}/{baz}/{bax}/foo", + "/{fod}/baz/bax/foo", + "/{foo}/baz/bax", + "/{bar}/{bay}/bay", + "/s", + "/s/s", + "/s/s/s", + "/s/s/s/s", + "/s/s/{s}/x", + "/s/s/{y}/d", + ], + matches: vec![ + ("/x/foo/bar", "/x/{foo}/bar", p! { "foo" => "foo" }), + ("/x/foo/baz", "/x/{bar}/baz", p! { "bar" => "foo" }), + ( + "/y/foo/baz", + "/{foo}/{bar}/baz", + p! { "foo" => "y", "bar" => "foo" }, + ), + ( + "/y/foo/bax", + "/{foo}/{baz}/bax", + p! { "foo" => "y", "baz" => "foo" }, + ), + ( + "/y/baz/baz", + "/{foo}/{bar}/baz", + p! { "foo" => "y", "bar" => "baz" }, + ), + ("/y/baz/bax/foo", "/{fod}/baz/bax/foo", p! { "fod" => "y" }), + ( + "/y/baz/b/foo", + "/{fod}/{baz}/{bax}/foo", + p! { "fod" => "y", "baz" => "baz", "bax" => "b" }, + ), + ("/y/baz/bax", "/{foo}/baz/bax", p! { "foo" => "y" }), + ( + "/z/bar/bay", + "/{bar}/{bay}/bay", + p! { "bar" => "z", "bay" => "bar" }, + ), + ("/s", "/s", p! {}), + ("/s/s", "/s/s", p! {}), + ("/s/s/s", "/s/s/s", p! {}), + ("/s/s/s/s", "/s/s/s/s", p! {}), + ("/s/s/s/x", "/s/s/{s}/x", p! { "s" => "s" }), + ("/s/s/s/d", "/s/s/{y}/d", p! { "y" => "s" }), + ], + } + .run() +} + +#[test] +fn blog() { + MatchTest { + routes: vec![ + "/{page}", + "/posts/{year}/{month}/{post}", + "/posts/{year}/{month}/index", + "/posts/{year}/top", + "/static/{*path}", + "/favicon.ico", + ], + matches: vec![ + ("/about", "/{page}", p! { "page" => "about" }), + ( + "/posts/2021/01/rust", + "/posts/{year}/{month}/{post}", + p! { "year" => "2021", "month" => "01", "post" => "rust" }, + ), + ( + "/posts/2021/01/index", + "/posts/{year}/{month}/index", + p! { "year" => "2021", "month" => "01" }, + ), + ( + "/posts/2021/top", + "/posts/{year}/top", + p! { "year" => "2021" }, + ), + ( + "/static/foo.png", + "/static/{*path}", + p! { "path" => "foo.png" }, + ), + ("/favicon.ico", "/favicon.ico", p! {}), + ], + } + .run() +} + +#[test] +fn double_overlap() { + MatchTest { + routes: vec![ + "/{object}/{id}", + "/secret/{id}/path", + "/secret/978", + "/other/{object}/{id}/", + "/other/an_object/{id}", + "/other/static/path", + "/other/long/static/path/", + ], + matches: vec![ + ( + "/secret/978/path", + "/secret/{id}/path", + p! { "id" => "978" }, + ), + ( + "/some_object/978", + "/{object}/{id}", + p! { "object" => "some_object", "id" => "978" }, + ), + ("/secret/978", "/secret/978", p! {}), + ("/super_secret/978/", "/{object}/{id}", Err(())), + ( + "/other/object/1/", + "/other/{object}/{id}/", + p! { "object" => "object", "id" => "1" }, + ), + ("/other/object/1/2", "/other/{object}/{id}", Err(())), + ( + "/other/an_object/1", + "/other/an_object/{id}", + p! { "id" => "1" }, + ), + ("/other/static/path", "/other/static/path", p! {}), + ( + "/other/long/static/path/", + "/other/long/static/path/", + p! {}, + ), + ], + } + .run() +} + +#[test] +fn catchall_off_by_one() { + MatchTest { + routes: vec!["/foo/{*catchall}", "/bar", "/bar/", "/bar/{*catchall}"], + matches: vec![ + ("/foo", "", Err(())), + ("/foo/", "", Err(())), + ("/foo/x", "/foo/{*catchall}", p! { "catchall" => "x" }), + ("/bar", "/bar", p! {}), + ("/bar/", "/bar/", p! {}), + ("/bar/x", "/bar/{*catchall}", p! { "catchall" => "x" }), + ], + } + .run() +} + +#[test] +fn overlap() { + MatchTest { + routes: vec![ + "/foo", + "/bar", + "/{*bar}", + "/baz", + "/baz/", + "/baz/x", + "/baz/{xxx}", + "/", + "/xxx/{*x}", + "/xxx/", + ], + matches: vec![ + ("/foo", "/foo", p! {}), + ("/bar", "/bar", p! {}), + ("/baz", "/baz", p! {}), + ("/baz/", "/baz/", p! {}), + ("/baz/x", "/baz/x", p! {}), + ("/???", "/{*bar}", p! { "bar" => "???" }), + ("/", "/", p! {}), + ("", "", Err(())), + ("/xxx/y", "/xxx/{*x}", p! { "x" => "y" }), + ("/xxx/", "/xxx/", p! {}), + ("/xxx", "/{*bar}", p! { "bar" => "xxx" }), + ], + } + .run() +} + +#[test] +fn missing_trailing_slash_param() { + MatchTest { + routes: vec!["/foo/{object}/{id}", "/foo/bar/baz", "/foo/secret/978/"], + matches: vec![ + ("/foo/secret/978/", "/foo/secret/978/", p! {}), + ( + "/foo/secret/978", + "/foo/{object}/{id}", + p! { "object" => "secret", "id" => "978" }, + ), + ], + } + .run() +} + +#[test] +fn extra_trailing_slash_param() { + MatchTest { + routes: vec!["/foo/{object}/{id}", "/foo/bar/baz", "/foo/secret/978"], + matches: vec![ + ("/foo/secret/978/", "", Err(())), + ("/foo/secret/978", "/foo/secret/978", p! {}), + ], + } + .run() +} + +#[test] +fn missing_trailing_slash_catch_all() { + MatchTest { + routes: vec!["/foo/{*bar}", "/foo/bar/baz", "/foo/secret/978/"], + matches: vec![ + ( + "/foo/secret/978", + "/foo/{*bar}", + p! { "bar" => "secret/978" }, + ), + ("/foo/secret/978/", "/foo/secret/978/", p! {}), + ], + } + .run() +} + +#[test] +fn extra_trailing_slash_catch_all() { + MatchTest { + routes: vec!["/foo/{*bar}", "/foo/bar/baz", "/foo/secret/978"], + matches: vec![ + ( + "/foo/secret/978/", + "/foo/{*bar}", + p! { "bar" => "secret/978/" }, + ), + ("/foo/secret/978", "/foo/secret/978", p! {}), + ], + } + .run() +} + +#[test] +fn double_overlap_trailing_slash() { + MatchTest { + routes: vec![ + "/{object}/{id}", + "/secret/{id}/path", + "/secret/978/", + "/other/{object}/{id}/", + "/other/an_object/{id}", + "/other/static/path", + "/other/long/static/path/", + ], + matches: vec![ + ("/secret/978/path/", "", Err(())), + ("/object/id/", "", Err(())), + ("/object/id/path", "", Err(())), + ("/other/object/1", "", Err(())), + ("/other/object/1/2", "", Err(())), + ( + "/other/an_object/1/", + "/other/{object}/{id}/", + p! { "object" => "an_object", "id" => "1" }, + ), + ( + "/other/static/path/", + "/other/{object}/{id}/", + p! { "object" => "static", "id" => "path" }, + ), + ("/other/long/static/path", "", Err(())), + ("/other/object/static/path", "", Err(())), + ], + } + .run() +} + +#[test] +fn trailing_slash_overlap() { + MatchTest { + routes: vec!["/foo/{x}/baz/", "/foo/{x}/baz", "/foo/bar/bar"], + matches: vec![ + ("/foo/x/baz/", "/foo/{x}/baz/", p! { "x" => "x" }), + ("/foo/x/baz", "/foo/{x}/baz", p! { "x" => "x" }), + ("/foo/bar/bar", "/foo/bar/bar", p! {}), + ], + } + .run() +} + +#[test] +fn trailing_slash() { + MatchTest { + routes: vec![ + "/hi", + "/b/", + "/search/{query}", + "/cmd/{tool}/", + "/src/{*filepath}", + "/x", + "/x/y", + "/y/", + "/y/z", + "/0/{id}", + "/0/{id}/1", + "/1/{id}/", + "/1/{id}/2", + "/aa", + "/a/", + "/admin", + "/admin/static", + "/admin/{category}", + "/admin/{category}/{page}", + "/doc", + "/doc/rust_faq.html", + "/doc/rust1.26.html", + "/no/a", + "/no/b", + "/no/a/b/{*other}", + "/api/{page}/{name}", + "/api/hello/{name}/bar/", + "/api/bar/{name}", + "/api/baz/foo", + "/api/baz/foo/bar", + "/foo/{p}", + ], + matches: vec![ + ("/hi/", "", Err(())), + ("/b", "", Err(())), + ("/search/rustacean/", "", Err(())), + ("/cmd/vet", "", Err(())), + ("/src", "", Err(())), + ("/src/", "", Err(())), + ("/x/", "", Err(())), + ("/y", "", Err(())), + ("/0/rust/", "", Err(())), + ("/1/rust", "", Err(())), + ("/a", "", Err(())), + ("/admin/", "", Err(())), + ("/doc/", "", Err(())), + ("/admin/static/", "", Err(())), + ("/admin/cfg/", "", Err(())), + ("/admin/cfg/users/", "", Err(())), + ("/api/hello/x/bar", "", Err(())), + ("/api/baz/foo/", "", Err(())), + ("/api/baz/bax/", "", Err(())), + ("/api/bar/huh/", "", Err(())), + ("/api/baz/foo/bar/", "", Err(())), + ("/api/world/abc/", "", Err(())), + ("/foo/pp/", "", Err(())), + ("/", "", Err(())), + ("/no", "", Err(())), + ("/no/", "", Err(())), + ("/no/a/b", "", Err(())), + ("/no/a/b/", "", Err(())), + ("/_", "", Err(())), + ("/_/", "", Err(())), + ("/api", "", Err(())), + ("/api/", "", Err(())), + ("/api/hello/x/foo", "", Err(())), + ("/api/baz/foo/bad", "", Err(())), + ("/foo/p/p", "", Err(())), + ], + } + .run() +} + +#[test] +fn backtracking_trailing_slash() { + MatchTest { + routes: vec!["/a/{b}/{c}", "/a/b/{c}/d/"], + matches: vec![("/a/b/c/d", "", Err(()))], + } + .run() +} + +#[test] +fn root_trailing_slash() { + MatchTest { + routes: vec!["/foo", "/bar", "/{baz}"], + matches: vec![("/", "", Err(()))], + } + .run() +} + +#[test] +fn catchall_overlap() { + MatchTest { + routes: vec!["/yyy/{*x}", "/yyy{*x}"], + matches: vec![ + ("/yyy/y", "/yyy/{*x}", p! { "x" => "y" }), + ("/yyy/", "/yyy{*x}", p! { "x" => "/" }), + ], + } + .run(); +} + +#[test] +fn escaped() { + MatchTest { + routes: vec![ + "/", + "/{{", + "/}}", + "/{{x", + "/}}y{{", + "/xy{{", + "/{{/xyz", + "/{ba{{r}", + "/{ba{{r}/", + "/{ba{{r}/x", + "/baz/{xxx}", + "/baz/{xxx}/xy{{", + "/baz/{xxx}/}}xy{{{{", + "/{{/{x}", + "/xxx/", + "/xxx/{x}}{{}}}}{{}}{{{{}}y}", + ], + matches: vec![ + ("/", "/", p! {}), + ("/{", "/{{", p! {}), + ("/}", "/}}", p! {}), + ("/{x", "/{{x", p! {}), + ("/}y{", "/}}y{{", p! {}), + ("/xy{", "/xy{{", p! {}), + ("/{/xyz", "/{{/xyz", p! {}), + ("/foo", "/{ba{{r}", p! { "ba{r" => "foo" }), + ("/{{", "/{ba{{r}", p! { "ba{r" => "{{" }), + ("/{{}}/", "/{ba{{r}/", p! { "ba{r" => "{{}}" }), + ("/{{}}{{/x", "/{ba{{r}/x", p! { "ba{r" => "{{}}{{" }), + ("/baz/x", "/baz/{xxx}", p! { "xxx" => "x" }), + ("/baz/x/xy{", "/baz/{xxx}/xy{{", p! { "xxx" => "x" }), + ("/baz/x/xy{{", "", Err(())), + ("/baz/x/}xy{{", "/baz/{xxx}/}}xy{{{{", p! { "xxx" => "x" }), + ("/{/{{", "/{{/{x}", p! { "x" => "{{" }), + ("/xxx", "/{ba{{r}", p! { "ba{r" => "xxx" }), + ("/xxx/", "/xxx/", p!()), + ( + "/xxx/foo", + "/xxx/{x}}{{}}}}{{}}{{{{}}y}", + p! { "x}{}}{}{{}y" => "foo" }, + ), + ], + } + .run() +} + +#[test] +fn basic() { + MatchTest { + routes: vec![ + "/hi", + "/contact", + "/co", + "/c", + "/a", + "/ab", + "/doc/", + "/doc/rust_faq.html", + "/doc/rust1.26.html", + "/ʯ", + "/β", + "/sd!here", + "/sd$here", + "/sd&here", + "/sd'here", + "/sd(here", + "/sd)here", + "/sd+here", + "/sd,here", + "/sd;here", + "/sd=here", + ], + matches: vec![ + ("/a", "/a", p! {}), + ("", "/", Err(())), + ("/hi", "/hi", p! {}), + ("/contact", "/contact", p! {}), + ("/co", "/co", p! {}), + ("", "/con", Err(())), + ("", "/cona", Err(())), + ("", "/no", Err(())), + ("/ab", "/ab", p! {}), + ("/ʯ", "/ʯ", p! {}), + ("/β", "/β", p! {}), + ("/sd!here", "/sd!here", p! {}), + ("/sd$here", "/sd$here", p! {}), + ("/sd&here", "/sd&here", p! {}), + ("/sd'here", "/sd'here", p! {}), + ("/sd(here", "/sd(here", p! {}), + ("/sd)here", "/sd)here", p! {}), + ("/sd+here", "/sd+here", p! {}), + ("/sd,here", "/sd,here", p! {}), + ("/sd;here", "/sd;here", p! {}), + ("/sd=here", "/sd=here", p! {}), + ], + } + .run() +} + +#[test] +fn wildcard() { + MatchTest { + routes: vec![ + "/", + "/cmd/{tool}/", + "/cmd/{tool2}/{sub}", + "/cmd/whoami", + "/cmd/whoami/root", + "/cmd/whoami/root/", + "/src", + "/src/", + "/src/{*filepath}", + "/search/", + "/search/{query}", + "/search/actix-web", + "/search/google", + "/user_{name}", + "/user_{name}/about", + "/files/{dir}/{*filepath}", + "/doc/", + "/doc/rust_faq.html", + "/doc/rust1.26.html", + "/info/{user}/public", + "/info/{user}/project/{project}", + "/info/{user}/project/rustlang", + "/aa/{*xx}", + "/ab/{*xx}", + "/ab/hello{*xx}", + "/{cc}", + "/c1/{dd}/e", + "/c1/{dd}/e1", + "/{cc}/cc", + "/{cc}/{dd}/ee", + "/{cc}/{dd}/{ee}/ff", + "/{cc}/{dd}/{ee}/{ff}/gg", + "/{cc}/{dd}/{ee}/{ff}/{gg}/hh", + "/get/test/abc/", + "/get/{param}/abc/", + "/something/{paramname}/thirdthing", + "/something/secondthing/test", + "/get/abc", + "/get/{param}", + "/get/abc/123abc", + "/get/abc/{param}", + "/get/abc/123abc/xxx8", + "/get/abc/123abc/{param}", + "/get/abc/123abc/xxx8/1234", + "/get/abc/123abc/xxx8/{param}", + "/get/abc/123abc/xxx8/1234/ffas", + "/get/abc/123abc/xxx8/1234/{param}", + "/get/abc/123abc/xxx8/1234/kkdd/12c", + "/get/abc/123abc/xxx8/1234/kkdd/{param}", + "/get/abc/{param}/test", + "/get/abc/123abd/{param}", + "/get/abc/123abddd/{param}", + "/get/abc/123/{param}", + "/get/abc/123abg/{param}", + "/get/abc/123abf/{param}", + "/get/abc/123abfff/{param}", + ], + matches: vec![ + ("/", "/", p! {}), + ("/cmd/test", "/cmd/{tool}/", Err(())), + ("/cmd/test/", "/cmd/{tool}/", p! { "tool" => "test" }), + ( + "/cmd/test/3", + "/cmd/{tool2}/{sub}", + p! { "tool2" => "test", "sub" => "3" }, + ), + ("/cmd/who", "/cmd/{tool}/", Err(())), + ("/cmd/who/", "/cmd/{tool}/", p! { "tool" => "who" }), + ("/cmd/whoami", "/cmd/whoami", p! {}), + ("/cmd/whoami/", "/cmd/{tool}/", p! { "tool" => "whoami" }), + ( + "/cmd/whoami/r", + "/cmd/{tool2}/{sub}", + p! { "tool2" => "whoami", "sub" => "r" }, + ), + ("/cmd/whoami/r/", "/cmd/{tool}/{sub}", Err(())), + ("/cmd/whoami/root", "/cmd/whoami/root", p! {}), + ("/cmd/whoami/root/", "/cmd/whoami/root/", p! {}), + ("/src", "/src", p! {}), + ("/src/", "/src/", p! {}), + ( + "/src/some/file.png", + "/src/{*filepath}", + p! { "filepath" => "some/file.png" }, + ), + ("/search/", "/search/", p! {}), + ( + "/search/actix", + "/search/{query}", + p! { "query" => "actix" }, + ), + ("/search/actix-web", "/search/actix-web", p! {}), + ( + "/search/someth!ng+in+ünìcodé", + "/search/{query}", + p! { "query" => "someth!ng+in+ünìcodé" }, + ), + ("/search/someth!ng+in+ünìcodé/", "", Err(())), + ( + "/user_rustacean", + "/user_{name}", + p! { "name" => "rustacean" }, + ), + ( + "/user_rustacean/about", + "/user_{name}/about", + p! { "name" => "rustacean" }, + ), + ( + "/files/js/inc/framework.js", + "/files/{dir}/{*filepath}", + p! { "dir" => "js", "filepath" => "inc/framework.js" }, + ), + ( + "/info/gordon/public", + "/info/{user}/public", + p! { "user" => "gordon" }, + ), + ( + "/info/gordon/project/rust", + "/info/{user}/project/{project}", + p! { "user" => "gordon", "project" => "rust" }, + ), + ( + "/info/gordon/project/rustlang", + "/info/{user}/project/rustlang", + p! { "user" => "gordon" }, + ), + ("/aa/", "/", Err(())), + ("/aa/aa", "/aa/{*xx}", p! { "xx" => "aa" }), + ("/ab/ab", "/ab/{*xx}", p! { "xx" => "ab" }), + ("/ab/hello-world", "/ab/hello{*xx}", p! { "xx" => "-world" }), + ("/a", "/{cc}", p! { "cc" => "a" }), + ("/all", "/{cc}", p! { "cc" => "all" }), + ("/d", "/{cc}", p! { "cc" => "d" }), + ("/ad", "/{cc}", p! { "cc" => "ad" }), + ("/dd", "/{cc}", p! { "cc" => "dd" }), + ("/dddaa", "/{cc}", p! { "cc" => "dddaa" }), + ("/aa", "/{cc}", p! { "cc" => "aa" }), + ("/aaa", "/{cc}", p! { "cc" => "aaa" }), + ("/aaa/cc", "/{cc}/cc", p! { "cc" => "aaa" }), + ("/ab", "/{cc}", p! { "cc" => "ab" }), + ("/abb", "/{cc}", p! { "cc" => "abb" }), + ("/abb/cc", "/{cc}/cc", p! { "cc" => "abb" }), + ("/allxxxx", "/{cc}", p! { "cc" => "allxxxx" }), + ("/alldd", "/{cc}", p! { "cc" => "alldd" }), + ("/all/cc", "/{cc}/cc", p! { "cc" => "all" }), + ("/a/cc", "/{cc}/cc", p! { "cc" => "a" }), + ("/c1/d/e", "/c1/{dd}/e", p! { "dd" => "d" }), + ("/c1/d/e1", "/c1/{dd}/e1", p! { "dd" => "d" }), + ( + "/c1/d/ee", + "/{cc}/{dd}/ee", + p! { "cc" => "c1", "dd" => "d" }, + ), + ("/cc/cc", "/{cc}/cc", p! { "cc" => "cc" }), + ("/ccc/cc", "/{cc}/cc", p! { "cc" => "ccc" }), + ("/deedwjfs/cc", "/{cc}/cc", p! { "cc" => "deedwjfs" }), + ("/acllcc/cc", "/{cc}/cc", p! { "cc" => "acllcc" }), + ("/get/test/abc/", "/get/test/abc/", p! {}), + ("/get/te/abc/", "/get/{param}/abc/", p! { "param" => "te" }), + ( + "/get/testaa/abc/", + "/get/{param}/abc/", + p! { "param" => "testaa" }, + ), + ("/get/xx/abc/", "/get/{param}/abc/", p! { "param" => "xx" }), + ("/get/tt/abc/", "/get/{param}/abc/", p! { "param" => "tt" }), + ("/get/a/abc/", "/get/{param}/abc/", p! { "param" => "a" }), + ("/get/t/abc/", "/get/{param}/abc/", p! { "param" => "t" }), + ("/get/aa/abc/", "/get/{param}/abc/", p! { "param" => "aa" }), + ( + "/get/abas/abc/", + "/get/{param}/abc/", + p! { "param" => "abas" }, + ), + ( + "/something/secondthing/test", + "/something/secondthing/test", + p! {}, + ), + ( + "/something/abcdad/thirdthing", + "/something/{paramname}/thirdthing", + p! { "paramname" => "abcdad" }, + ), + ( + "/something/secondthingaaaa/thirdthing", + "/something/{paramname}/thirdthing", + p! { "paramname" => "secondthingaaaa" }, + ), + ( + "/something/se/thirdthing", + "/something/{paramname}/thirdthing", + p! { "paramname" => "se" }, + ), + ( + "/something/s/thirdthing", + "/something/{paramname}/thirdthing", + p! { "paramname" => "s" }, + ), + ("/c/d/ee", "/{cc}/{dd}/ee", p! { "cc" => "c", "dd" => "d" }), + ( + "/c/d/e/ff", + "/{cc}/{dd}/{ee}/ff", + p! { "cc" => "c", "dd" => "d", "ee" => "e" }, + ), + ( + "/c/d/e/f/gg", + "/{cc}/{dd}/{ee}/{ff}/gg", + p! { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f" }, + ), + ( + "/c/d/e/f/g/hh", + "/{cc}/{dd}/{ee}/{ff}/{gg}/hh", + p! { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f", "gg" => "g" }, + ), + ( + "/cc/dd/ee/ff/gg/hh", + "/{cc}/{dd}/{ee}/{ff}/{gg}/hh", + p! { "cc" => "cc", "dd" => "dd", "ee" => "ee", "ff" => "ff", "gg" => "gg" }, + ), + ("/get/abc", "/get/abc", p! {}), + ("/get/a", "/get/{param}", p! { "param" => "a" }), + ("/get/abz", "/get/{param}", p! { "param" => "abz" }), + ("/get/12a", "/get/{param}", p! { "param" => "12a" }), + ("/get/abcd", "/get/{param}", p! { "param" => "abcd" }), + ("/get/abc/123abc", "/get/abc/123abc", p! {}), + ("/get/abc/12", "/get/abc/{param}", p! { "param" => "12" }), + ( + "/get/abc/123ab", + "/get/abc/{param}", + p! { "param" => "123ab" }, + ), + ("/get/abc/xyz", "/get/abc/{param}", p! { "param" => "xyz" }), + ( + "/get/abc/123abcddxx", + "/get/abc/{param}", + p! { "param" => "123abcddxx" }, + ), + ("/get/abc/123abc/xxx8", "/get/abc/123abc/xxx8", p! {}), + ( + "/get/abc/123abc/x", + "/get/abc/123abc/{param}", + p! { "param" => "x" }, + ), + ( + "/get/abc/123abc/xxx", + "/get/abc/123abc/{param}", + p! { "param" => "xxx" }, + ), + ( + "/get/abc/123abc/abc", + "/get/abc/123abc/{param}", + p! { "param" => "abc" }, + ), + ( + "/get/abc/123abc/xxx8xxas", + "/get/abc/123abc/{param}", + p! { "param" => "xxx8xxas" }, + ), + ( + "/get/abc/123abc/xxx8/1234", + "/get/abc/123abc/xxx8/1234", + p! {}, + ), + ( + "/get/abc/123abc/xxx8/1", + "/get/abc/123abc/xxx8/{param}", + p! { "param" => "1" }, + ), + ( + "/get/abc/123abc/xxx8/123", + "/get/abc/123abc/xxx8/{param}", + p! { "param" => "123" }, + ), + ( + "/get/abc/123abc/xxx8/78k", + "/get/abc/123abc/xxx8/{param}", + p! { "param" => "78k" }, + ), + ( + "/get/abc/123abc/xxx8/1234xxxd", + "/get/abc/123abc/xxx8/{param}", + p! { "param" => "1234xxxd" }, + ), + ( + "/get/abc/123abc/xxx8/1234/ffas", + "/get/abc/123abc/xxx8/1234/ffas", + p! {}, + ), + ( + "/get/abc/123abc/xxx8/1234/f", + "/get/abc/123abc/xxx8/1234/{param}", + p! { "param" => "f" }, + ), + ( + "/get/abc/123abc/xxx8/1234/ffa", + "/get/abc/123abc/xxx8/1234/{param}", + p! { "param" => "ffa" }, + ), + ( + "/get/abc/123abc/xxx8/1234/kka", + "/get/abc/123abc/xxx8/1234/{param}", + p! { "param" => "kka" }, + ), + ( + "/get/abc/123abc/xxx8/1234/ffas321", + "/get/abc/123abc/xxx8/1234/{param}", + p! { "param" => "ffas321" }, + ), + ( + "/get/abc/123abc/xxx8/1234/kkdd/12c", + "/get/abc/123abc/xxx8/1234/kkdd/12c", + p! {}, + ), + ( + "/get/abc/123abc/xxx8/1234/kkdd/1", + "/get/abc/123abc/xxx8/1234/kkdd/{param}", + p! { "param" => "1" }, + ), + ( + "/get/abc/123abc/xxx8/1234/kkdd/12", + "/get/abc/123abc/xxx8/1234/kkdd/{param}", + p! { "param" => "12" }, + ), + ( + "/get/abc/123abc/xxx8/1234/kkdd/12b", + "/get/abc/123abc/xxx8/1234/kkdd/{param}", + p! { "param" => "12b" }, + ), + ( + "/get/abc/123abc/xxx8/1234/kkdd/34", + "/get/abc/123abc/xxx8/1234/kkdd/{param}", + p! { "param" => "34" }, + ), + ( + "/get/abc/123abc/xxx8/1234/kkdd/12c2e3", + "/get/abc/123abc/xxx8/1234/kkdd/{param}", + p! { "param" => "12c2e3" }, + ), + ( + "/get/abc/12/test", + "/get/abc/{param}/test", + p! { "param" => "12" }, + ), + ( + "/get/abc/123abdd/test", + "/get/abc/{param}/test", + p! { "param" => "123abdd" }, + ), + ( + "/get/abc/123abdddf/test", + "/get/abc/{param}/test", + p! { "param" => "123abdddf" }, + ), + ( + "/get/abc/123ab/test", + "/get/abc/{param}/test", + p! { "param" => "123ab" }, + ), + ( + "/get/abc/123abgg/test", + "/get/abc/{param}/test", + p! { "param" => "123abgg" }, + ), + ( + "/get/abc/123abff/test", + "/get/abc/{param}/test", + p! { "param" => "123abff" }, + ), + ( + "/get/abc/123abffff/test", + "/get/abc/{param}/test", + p! { "param" => "123abffff" }, + ), + ( + "/get/abc/123abd/test", + "/get/abc/123abd/{param}", + p! { "param" => "test" }, + ), + ( + "/get/abc/123abddd/test", + "/get/abc/123abddd/{param}", + p! { "param" => "test" }, + ), + ( + "/get/abc/123/test22", + "/get/abc/123/{param}", + p! { "param" => "test22" }, + ), + ( + "/get/abc/123abg/test", + "/get/abc/123abg/{param}", + p! { "param" => "test" }, + ), + ( + "/get/abc/123abf/testss", + "/get/abc/123abf/{param}", + p! { "param" => "testss" }, + ), + ( + "/get/abc/123abfff/te", + "/get/abc/123abfff/{param}", + p! { "param" => "te" }, + ), + ], + } + .run() +} diff --git a/vendor/matchit/tests/remove.rs b/vendor/matchit/tests/remove.rs new file mode 100644 index 00000000..3237ccde --- /dev/null +++ b/vendor/matchit/tests/remove.rs @@ -0,0 +1,265 @@ +use matchit::Router; + +struct RemoveTest { + routes: Vec<&'static str>, + ops: Vec<(Operation, &'static str, Option<&'static str>)>, + remaining: Vec<&'static str>, +} + +enum Operation { + Insert, + Remove, +} + +use Operation::*; + +impl RemoveTest { + fn run(self) { + let mut router = Router::new(); + + for route in self.routes.iter() { + assert_eq!(router.insert(*route, route.to_owned()), Ok(()), "{route}"); + } + + for (op, route, expected) in self.ops.iter() { + match op { + Insert => { + assert_eq!(router.insert(*route, route), Ok(()), "{route}") + } + Remove => { + assert_eq!(router.remove(*route), *expected, "removing {route}",) + } + } + } + + for route in self.remaining { + assert!(matches!(router.at(route), Ok(_)), "remaining {route}"); + } + } +} + +#[test] +fn normalized() { + RemoveTest { + routes: vec![ + "/x/{foo}/bar", + "/x/{bar}/baz", + "/{foo}/{baz}/bax", + "/{foo}/{bar}/baz", + "/{fod}/{baz}/{bax}/foo", + "/{fod}/baz/bax/foo", + "/{foo}/baz/bax", + "/{bar}/{bay}/bay", + "/s", + "/s/s", + "/s/s/s", + "/s/s/s/s", + "/s/s/{s}/x", + "/s/s/{y}/d", + ], + ops: vec![ + (Remove, "/x/{foo}/bar", Some("/x/{foo}/bar")), + (Remove, "/x/{bar}/baz", Some("/x/{bar}/baz")), + (Remove, "/{foo}/{baz}/bax", Some("/{foo}/{baz}/bax")), + (Remove, "/{foo}/{bar}/baz", Some("/{foo}/{bar}/baz")), + ( + Remove, + "/{fod}/{baz}/{bax}/foo", + Some("/{fod}/{baz}/{bax}/foo"), + ), + (Remove, "/{fod}/baz/bax/foo", Some("/{fod}/baz/bax/foo")), + (Remove, "/{foo}/baz/bax", Some("/{foo}/baz/bax")), + (Remove, "/{bar}/{bay}/bay", Some("/{bar}/{bay}/bay")), + (Remove, "/s", Some("/s")), + (Remove, "/s/s", Some("/s/s")), + (Remove, "/s/s/s", Some("/s/s/s")), + (Remove, "/s/s/s/s", Some("/s/s/s/s")), + (Remove, "/s/s/{s}/x", Some("/s/s/{s}/x")), + (Remove, "/s/s/{y}/d", Some("/s/s/{y}/d")), + ], + remaining: vec![], + } + .run(); +} + +#[test] +fn test() { + RemoveTest { + routes: vec!["/home", "/home/{id}"], + ops: vec![ + (Remove, "/home", Some("/home")), + (Remove, "/home", None), + (Remove, "/home/{id}", Some("/home/{id}")), + (Remove, "/home/{id}", None), + ], + remaining: vec![], + } + .run(); +} + +#[test] +fn blog() { + RemoveTest { + routes: vec![ + "/{page}", + "/posts/{year}/{month}/{post}", + "/posts/{year}/{month}/index", + "/posts/{year}/top", + "/static/{*path}", + "/favicon.ico", + ], + ops: vec![ + (Remove, "/{page}", Some("/{page}")), + ( + Remove, + "/posts/{year}/{month}/{post}", + Some("/posts/{year}/{month}/{post}"), + ), + ( + Remove, + "/posts/{year}/{month}/index", + Some("/posts/{year}/{month}/index"), + ), + (Remove, "/posts/{year}/top", Some("/posts/{year}/top")), + (Remove, "/static/{*path}", Some("/static/{*path}")), + (Remove, "/favicon.ico", Some("/favicon.ico")), + ], + remaining: vec![], + } + .run() +} + +#[test] +fn catchall() { + RemoveTest { + routes: vec!["/foo/{*catchall}", "/bar", "/bar/", "/bar/{*catchall}"], + ops: vec![ + (Remove, "/foo/{*catchall}", Some("/foo/{*catchall}")), + (Remove, "/bar/", Some("/bar/")), + (Insert, "/foo/*catchall", Some("/foo/*catchall")), + (Remove, "/bar/{*catchall}", Some("/bar/{*catchall}")), + ], + remaining: vec!["/bar", "/foo/*catchall"], + } + .run(); +} + +#[test] +fn overlapping_routes() { + RemoveTest { + routes: vec![ + "/home", + "/home/{id}", + "/users", + "/users/{id}", + "/users/{id}/posts", + "/users/{id}/posts/{post_id}", + "/articles", + "/articles/{category}", + "/articles/{category}/{id}", + ], + ops: vec![ + (Remove, "/home", Some("/home")), + (Insert, "/home", Some("/home")), + (Remove, "/home/{id}", Some("/home/{id}")), + (Insert, "/home/{id}", Some("/home/{id}")), + (Remove, "/users", Some("/users")), + (Insert, "/users", Some("/users")), + (Remove, "/users/{id}", Some("/users/{id}")), + (Insert, "/users/{id}", Some("/users/{id}")), + (Remove, "/users/{id}/posts", Some("/users/{id}/posts")), + (Insert, "/users/{id}/posts", Some("/users/{id}/posts")), + ( + Remove, + "/users/{id}/posts/{post_id}", + Some("/users/{id}/posts/{post_id}"), + ), + ( + Insert, + "/users/{id}/posts/{post_id}", + Some("/users/{id}/posts/{post_id}"), + ), + (Remove, "/articles", Some("/articles")), + (Insert, "/articles", Some("/articles")), + (Remove, "/articles/{category}", Some("/articles/{category}")), + (Insert, "/articles/{category}", Some("/articles/{category}")), + ( + Remove, + "/articles/{category}/{id}", + Some("/articles/{category}/{id}"), + ), + ( + Insert, + "/articles/{category}/{id}", + Some("/articles/{category}/{id}"), + ), + ], + remaining: vec![ + "/home", + "/home/{id}", + "/users", + "/users/{id}", + "/users/{id}/posts", + "/users/{id}/posts/{post_id}", + "/articles", + "/articles/{category}", + "/articles/{category}/{id}", + ], + } + .run(); +} + +#[test] +fn trailing_slash() { + RemoveTest { + routes: vec!["/{home}/", "/foo"], + ops: vec![ + (Remove, "/", None), + (Remove, "/{home}", None), + (Remove, "/foo/", None), + (Remove, "/foo", Some("/foo")), + (Remove, "/{home}", None), + (Remove, "/{home}/", Some("/{home}/")), + ], + remaining: vec![], + } + .run(); +} + +#[test] +fn remove_root() { + RemoveTest { + routes: vec!["/"], + ops: vec![(Remove, "/", Some("/"))], + remaining: vec![], + } + .run(); +} + +#[test] +fn check_escaped_params() { + RemoveTest { + routes: vec![ + "/foo/{id}", + "/foo/{id}/bar", + "/bar/{user}/{id}", + "/bar/{user}/{id}/baz", + "/baz/{product}/{user}/{id}", + ], + ops: vec![ + (Remove, "/foo/{a}", None), + (Remove, "/foo/{a}/bar", None), + (Remove, "/bar/{a}/{b}", None), + (Remove, "/bar/{a}/{b}/baz", None), + (Remove, "/baz/{a}/{b}/{c}", None), + ], + remaining: vec![ + "/foo/{id}", + "/foo/{id}/bar", + "/bar/{user}/{id}", + "/bar/{user}/{id}/baz", + "/baz/{product}/{user}/{id}", + ], + } + .run(); +} -- cgit v1.2.3