diff --git a/Cargo.lock b/Cargo.lock index c36ef8f9..ad014b1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6681,12 +6681,6 @@ dependencies = [ "regex-syntax 0.8.5", ] -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - [[package]] name = "regex-syntax" version = "0.5.6" @@ -11566,7 +11560,6 @@ dependencies = [ "nanoid", "r2d2", "r2d2_sqlite", - "regex-lite", "rusqlite", "schemars 0.8.22", "sea-query", diff --git a/apps/yaak-client/routeTree.gen.ts b/apps/yaak-client/routeTree.gen.ts index 30ef4069..e0029e12 100644 --- a/apps/yaak-client/routeTree.gen.ts +++ b/apps/yaak-client/routeTree.gen.ts @@ -46,9 +46,9 @@ const WorkspacesWorkspaceIdRequestsRequestIdRoute = export interface FileRoutesByFullPath { '/': typeof IndexRoute - '/workspaces': typeof WorkspacesIndexRoute + '/workspaces/': typeof WorkspacesIndexRoute '/workspaces/$workspaceId/settings': typeof WorkspacesWorkspaceIdSettingsRoute - '/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdIndexRoute + '/workspaces/$workspaceId/': typeof WorkspacesWorkspaceIdIndexRoute '/workspaces/$workspaceId/requests/$requestId': typeof WorkspacesWorkspaceIdRequestsRequestIdRoute } export interface FileRoutesByTo { @@ -70,9 +70,9 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' - | '/workspaces' + | '/workspaces/' | '/workspaces/$workspaceId/settings' - | '/workspaces/$workspaceId' + | '/workspaces/$workspaceId/' | '/workspaces/$workspaceId/requests/$requestId' fileRoutesByTo: FileRoutesByTo to: @@ -110,14 +110,14 @@ declare module '@tanstack/react-router' { '/workspaces/': { id: '/workspaces/' path: '/workspaces' - fullPath: '/workspaces' + fullPath: '/workspaces/' preLoaderRoute: typeof WorkspacesIndexRouteImport parentRoute: typeof rootRouteImport } '/workspaces/$workspaceId/': { id: '/workspaces/$workspaceId/' path: '/workspaces/$workspaceId' - fullPath: '/workspaces/$workspaceId' + fullPath: '/workspaces/$workspaceId/' preLoaderRoute: typeof WorkspacesWorkspaceIdIndexRouteImport parentRoute: typeof rootRouteImport } diff --git a/crates/yaak-models/Cargo.toml b/crates/yaak-models/Cargo.toml index ae318887..e9cff8cb 100644 --- a/crates/yaak-models/Cargo.toml +++ b/crates/yaak-models/Cargo.toml @@ -11,7 +11,6 @@ hex = { workspace = true } include_dir = "0.7" log = { workspace = true } nanoid = "0.4.0" -regex-lite = "0.1" rusqlite = { version = "0.38", features = ["bundled", "chrono"] } sea-query = { version = "1.0", features = ["with-chrono", "attr"] } sea-query-rusqlite = { version = "0.8.0", features = ["with-chrono"] } diff --git a/crates/yaak-models/src/path_placeholders.rs b/crates/yaak-models/src/path_placeholders.rs index 8f39ad19..e1a1d086 100644 --- a/crates/yaak-models/src/path_placeholders.rs +++ b/crates/yaak-models/src/path_placeholders.rs @@ -34,27 +34,41 @@ fn replace_path_placeholder(p: &HttpUrlParameter, url: &str) -> String { return url.to_string(); } - // A path placeholder is terminated by `/`, `?`, `#`, end-of-string, or a literal `:`. - // The `:` boundary is what lets `/:id:increment-importance` substitute the `:id` - // placeholder while leaving `:increment-importance` as literal text. - let re = regex_lite::Regex::new(format!("(/){}([/?#:]|$)", p.name).as_str()).unwrap(); - let result = re - .replace_all(url, |cap: ®ex_lite::Captures| { - format!( - "{}{}{}", - cap[1].to_string(), - urlencoding::encode(p.value.as_str()), - cap[2].to_string() - ) - }) - .into_owned(); + // A placeholder is `/` followed by the parameter's name (which starts with `:`), and it + // ends at `/`, `?`, `#`, a literal `:`, or the end of the URL. The `:` boundary is what + // lets `/:id:increment-importance` substitute the `:id` placeholder while leaving + // `:increment-importance` as literal text. `/:foooo` is not a match for `:foo`. + // + // A plain scan rather than a regex: the name is matched literally, so a name containing + // `.` or `+` means exactly that, and nothing else in the model layer needs a regex engine. + let name = p.name.as_str(); + let value = urlencoding::encode(p.value.as_str()); + let mut result = String::with_capacity(url.len()); + let mut rest = url; + while let Some(slash) = rest.find('/') { + let after_slash = &rest[slash + 1..]; + let is_placeholder = after_slash.starts_with(name) + && after_slash[name.len()..] + .chars() + .next() + .is_none_or(|c| matches!(c, '/' | '?' | '#' | ':')); + if is_placeholder { + result.push_str(&rest[..=slash]); + result.push_str(&value); + rest = &after_slash[name.len()..]; + } else { + result.push_str(&rest[..=slash]); + rest = after_slash; + } + } + result.push_str(rest); result } #[cfg(test)] mod placeholder_tests { - use crate::path_placeholders::{apply_path_placeholders, replace_path_placeholder}; use crate::models::{HttpRequest, HttpUrlParameter}; + use crate::path_placeholders::{apply_path_placeholders, replace_path_placeholder}; #[test] fn placeholder_middle() { @@ -98,6 +112,30 @@ mod placeholder_tests { ); } + #[test] + fn placeholder_name_is_matched_literally() { + // `.` in a name is a dot, not "any character". + let p = HttpUrlParameter { + name: ":id.v2".into(), + value: "xxx".into(), + enabled: true, + id: None, + }; + assert_eq!( + replace_path_placeholder(&p, "https://example.com/:id.v2/:idXv2"), + "https://example.com/xxx/:idXv2", + ); + } + + #[test] + fn placeholder_repeated() { + let p = HttpUrlParameter { name: ":id".into(), value: "7".into(), enabled: true, id: None }; + assert_eq!( + replace_path_placeholder(&p, "https://example.com/:id/:id"), + "https://example.com/7/7", + ); + } + #[test] fn placeholder_missing() { let p = HttpUrlParameter { diff --git a/crates/yaak-web/pkg/yaak_web_bg.js b/crates/yaak-web/pkg/yaak_web_bg.js index d89d701f..638b5a25 100644 --- a/crates/yaak-web/pkg/yaak_web_bg.js +++ b/crates/yaak-web/pkg/yaak_web_bg.js @@ -694,7 +694,7 @@ export function __wbg_versions_215a3ab1c9d5745a(arg0) { return ret; } export function __wbindgen_cast_0000000000000001(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1123, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1116, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__ha1c2fa93df0107f3); return ret; } @@ -705,7 +705,7 @@ export function __wbindgen_cast_0000000000000002(arg0, arg1) { } export function __wbindgen_cast_0000000000000003(arg0, arg1) { // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("IDBVersionChangeEvent")], shim_idx: 114, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. - const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h999df771f7987dc3); + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hdf19cb46f9aecb24); return ret; } export function __wbindgen_cast_0000000000000004(arg0, arg1) { @@ -762,8 +762,8 @@ function wasm_bindgen__convert__closures_____invoke__ha1c2fa93df0107f3(arg0, arg } } -function wasm_bindgen__convert__closures_____invoke__h999df771f7987dc3(arg0, arg1, arg2) { - const ret = wasm.wasm_bindgen__convert__closures_____invoke__h999df771f7987dc3(arg0, arg1, arg2); +function wasm_bindgen__convert__closures_____invoke__hdf19cb46f9aecb24(arg0, arg1, arg2) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__hdf19cb46f9aecb24(arg0, arg1, arg2); if (ret[1]) { throw takeFromExternrefTable0(ret[0]); } diff --git a/crates/yaak-web/pkg/yaak_web_bg.wasm b/crates/yaak-web/pkg/yaak_web_bg.wasm index bce7f199..3b908d95 100644 Binary files a/crates/yaak-web/pkg/yaak_web_bg.wasm and b/crates/yaak-web/pkg/yaak_web_bg.wasm differ diff --git a/crates/yaak-web/pkg/yaak_web_bg.wasm.d.ts b/crates/yaak-web/pkg/yaak_web_bg.wasm.d.ts index d7cfef7c..9893bd63 100644 --- a/crates/yaak-web/pkg/yaak_web_bg.wasm.d.ts +++ b/crates/yaak-web/pkg/yaak_web_bg.wasm.d.ts @@ -18,7 +18,7 @@ export const rust_sqlite_wasm_realloc: (a: number, b: number) => number; export const sqlite3_os_end: () => number; export const sqlite3_os_init: () => number; export const wasm_bindgen__convert__closures_____invoke__ha1c2fa93df0107f3: (a: number, b: number, c: any) => [number, number]; -export const wasm_bindgen__convert__closures_____invoke__h999df771f7987dc3: (a: number, b: number, c: any) => [number, number]; +export const wasm_bindgen__convert__closures_____invoke__hdf19cb46f9aecb24: (a: number, b: number, c: any) => [number, number]; export const wasm_bindgen__convert__closures_____invoke__h2cf3f4cce3b29948: (a: number, b: number, c: any, d: any) => void; export const wasm_bindgen__convert__closures_____invoke__ha7903b6e296dd8f4: (a: number, b: number, c: any) => void; export const wasm_bindgen__convert__closures_____invoke__ha1b480b83daa641f: (a: number, b: number) => void;