From 8af28e34757d55d8901ca74b433c8d7b640923bd Mon Sep 17 00:00:00 2001 From: jkomyno Date: Mon, 14 Oct 2024 19:46:55 +0400 Subject: [PATCH 01/12] feat(crosstarget-utils): add Wasm-friendly regex util --- Cargo.lock | 1 + libs/crosstarget-utils/Cargo.toml | 1 + libs/crosstarget-utils/src/common.rs | 21 +++++++++++ libs/crosstarget-utils/src/native/mod.rs | 1 + libs/crosstarget-utils/src/native/regex.rs | 44 ++++++++++++++++++++++ libs/crosstarget-utils/src/wasm/mod.rs | 1 + libs/crosstarget-utils/src/wasm/regex.rs | 39 +++++++++++++++++++ 7 files changed, 108 insertions(+) create mode 100644 libs/crosstarget-utils/src/native/regex.rs create mode 100644 libs/crosstarget-utils/src/wasm/regex.rs diff --git a/Cargo.lock b/Cargo.lock index 8908c059635..8d2fa810e6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -926,6 +926,7 @@ dependencies = [ "futures", "js-sys", "pin-project", + "regex", "tokio", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/libs/crosstarget-utils/Cargo.toml b/libs/crosstarget-utils/Cargo.toml index 609832a99ef..81bbf540c69 100644 --- a/libs/crosstarget-utils/Cargo.toml +++ b/libs/crosstarget-utils/Cargo.toml @@ -17,3 +17,4 @@ pin-project = "1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio.workspace = true +regex.workspace = true diff --git a/libs/crosstarget-utils/src/common.rs b/libs/crosstarget-utils/src/common.rs index 92a1d5094e8..5337fa00760 100644 --- a/libs/crosstarget-utils/src/common.rs +++ b/libs/crosstarget-utils/src/common.rs @@ -21,3 +21,24 @@ impl Display for TimeoutError { } impl std::error::Error for TimeoutError {} + +#[derive(Debug)] +pub struct RegExpError { + pub message: String, +} + +#[derive(PartialEq)] +pub enum RegExpFlags { + IgnoreCase, + Multiline, +} + +impl From for String { + fn from(flags: RegExpFlags) -> Self { + match flags { + RegExpFlags::IgnoreCase => "i", + RegExpFlags::Multiline => "m", + } + .to_string() + } +} diff --git a/libs/crosstarget-utils/src/native/mod.rs b/libs/crosstarget-utils/src/native/mod.rs index b19a356ff8f..e3793a1de65 100644 --- a/libs/crosstarget-utils/src/native/mod.rs +++ b/libs/crosstarget-utils/src/native/mod.rs @@ -1,3 +1,4 @@ +pub mod regex; pub mod spawn; pub mod task; pub mod time; diff --git a/libs/crosstarget-utils/src/native/regex.rs b/libs/crosstarget-utils/src/native/regex.rs new file mode 100644 index 00000000000..fa03c25156c --- /dev/null +++ b/libs/crosstarget-utils/src/native/regex.rs @@ -0,0 +1,44 @@ +use regex::{Regex as NativeRegex, RegexBuilder}; + +use crate::common::{RegExpError, RegExpFlags}; + +pub struct RegExp { + inner: NativeRegex, +} + +impl RegExp { + pub fn new(pattern: &str, flags: Vec) -> Result { + let mut builder = RegexBuilder::new(pattern); + + if flags.contains(&RegExpFlags::Multiline) { + builder.multi_line(true); + } + + if flags.contains(&RegExpFlags::IgnoreCase) { + builder.case_insensitive(true); + } + + let inner = builder.build().map_err(|e| RegExpError { message: e.to_string() })?; + + Ok(Self { inner }) + } + + /// Searches for the first match of this regex in the haystack given, and if found, + /// returns not only the overall match but also the matches of each capture group in the regex. + /// If no match is found, then None is returned. + pub fn captures(&self, message: &str) -> Option> { + self.inner.captures(message).map(|captures| { + captures + .iter() + .map(|capture| capture.and_then(|cap| Some(cap.as_str().to_string()))) + .filter(|capture| capture.is_some()) + .map(|capture| capture.unwrap()) + .collect() + }) + } + + /// Tests if the regex matches the input string. + pub fn test(&self, message: &str) -> bool { + self.inner.is_match(message) + } +} diff --git a/libs/crosstarget-utils/src/wasm/mod.rs b/libs/crosstarget-utils/src/wasm/mod.rs index b19a356ff8f..e3793a1de65 100644 --- a/libs/crosstarget-utils/src/wasm/mod.rs +++ b/libs/crosstarget-utils/src/wasm/mod.rs @@ -1,3 +1,4 @@ +pub mod regex; pub mod spawn; pub mod task; pub mod time; diff --git a/libs/crosstarget-utils/src/wasm/regex.rs b/libs/crosstarget-utils/src/wasm/regex.rs new file mode 100644 index 00000000000..e75ea0081c9 --- /dev/null +++ b/libs/crosstarget-utils/src/wasm/regex.rs @@ -0,0 +1,39 @@ +use js_sys::RegExp as JSRegExp; + +use crate::common::{RegExpError, RegExpFlags}; + +pub struct RegExp { + inner: JSRegExp, +} + +impl RegExp { + pub fn new(pattern: &str, flags: Vec) -> Result { + let mut flags_as_str: String = flags.into_iter().map(|flag| String::from(flag)).collect(); + + // Global flag is implied in `regex::Regex`, so we match that behavior for consistency. + flags_as_str.push('g'); + + Ok(Self { + inner: JSRegExp::new(pattern, &flags_as_str), + }) + } + + /// Searches for the first match of this regex in the haystack given, and if found, + /// returns not only the overall match but also the matches of each capture group in the regex. + /// If no match is found, then None is returned. + pub fn captures(&self, message: &str) -> Option> { + let matches = self.inner.exec(message); + matches.and_then(|matches| { + let mut captures = Vec::new(); + for i in 0..matches.length() { + captures.push(matches.get(i).as_string().unwrap()); + } + Some(captures) + }) + } + + /// Tests if the regex matches the input string. + pub fn test(&self, input: &str) -> bool { + self.inner.test(input) + } +} From 888650e67cab4c03f4a55f406bdfcbd6985a8061 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Mon, 14 Oct 2024 19:47:51 +0400 Subject: [PATCH 02/12] fix(query-engine-wasm): reduce size of postgres Driver Adapter via Wasm-friendly regex util --- quaint/src/connector/postgres/error.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/quaint/src/connector/postgres/error.rs b/quaint/src/connector/postgres/error.rs index e578f7aa283..76f3ad132be 100644 --- a/quaint/src/connector/postgres/error.rs +++ b/quaint/src/connector/postgres/error.rs @@ -1,4 +1,4 @@ -use regex::Regex; +use crosstarget_utils::regex::RegExp; use std::fmt::{Display, Formatter}; use crate::error::{DatabaseConstraint, Error, ErrorKind, Name}; @@ -30,9 +30,8 @@ impl Display for PostgresError { } fn extract_fk_constraint_name(message: &str) -> Option { - let re = Regex::new(r#"foreign key constraint "([^"]+)""#).unwrap(); - re.captures(message) - .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) + let re = RegExp::new(r#"foreign key constraint "([^"]+)""#, vec![]).unwrap(); + re.captures(message).and_then(|caps| caps.get(1).cloned()) } impl From for Error { From 383b0d0ea49f81325efc4e5303a7c2aa1239a10e Mon Sep 17 00:00:00 2001 From: jkomyno Date: Mon, 14 Oct 2024 20:30:33 +0400 Subject: [PATCH 03/12] chore: fix clippy --- libs/crosstarget-utils/src/native/regex.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/crosstarget-utils/src/native/regex.rs b/libs/crosstarget-utils/src/native/regex.rs index fa03c25156c..0a93d4db104 100644 --- a/libs/crosstarget-utils/src/native/regex.rs +++ b/libs/crosstarget-utils/src/native/regex.rs @@ -30,9 +30,7 @@ impl RegExp { self.inner.captures(message).map(|captures| { captures .iter() - .map(|capture| capture.and_then(|cap| Some(cap.as_str().to_string()))) - .filter(|capture| capture.is_some()) - .map(|capture| capture.unwrap()) + .flat_map(|capture| capture.map(|cap| cap.as_str().to_string())) .collect() }) } From 86fc7d464624231af1fe4ab092105fc37c3bf64d Mon Sep 17 00:00:00 2001 From: jkomyno Date: Mon, 14 Oct 2024 20:35:59 +0400 Subject: [PATCH 04/12] chore: fix clippy --- libs/crosstarget-utils/src/wasm/regex.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/crosstarget-utils/src/wasm/regex.rs b/libs/crosstarget-utils/src/wasm/regex.rs index e75ea0081c9..67d0a0eb4c1 100644 --- a/libs/crosstarget-utils/src/wasm/regex.rs +++ b/libs/crosstarget-utils/src/wasm/regex.rs @@ -23,12 +23,12 @@ impl RegExp { /// If no match is found, then None is returned. pub fn captures(&self, message: &str) -> Option> { let matches = self.inner.exec(message); - matches.and_then(|matches| { + matches.map(|matches| { let mut captures = Vec::new(); for i in 0..matches.length() { captures.push(matches.get(i).as_string().unwrap()); } - Some(captures) + captures }) } From 5fd23c6d6986751434bf32d21708584c0ac3f9a5 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Mon, 14 Oct 2024 21:31:26 +0400 Subject: [PATCH 05/12] chore: review comments --- Cargo.lock | 2 ++ libs/crosstarget-utils/Cargo.toml | 1 + libs/crosstarget-utils/src/common.rs | 28 +++++++++++++++------- libs/crosstarget-utils/src/native/regex.rs | 9 +++---- libs/crosstarget-utils/src/wasm/regex.rs | 16 +++++++++---- quaint/Cargo.toml | 1 + quaint/src/connector/postgres/error.rs | 3 ++- 7 files changed, 41 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d2fa810e6e..9dee32574c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -923,6 +923,7 @@ dependencies = [ name = "crosstarget-utils" version = "0.1.0" dependencies = [ + "enumflags2", "futures", "js-sys", "pin-project", @@ -3832,6 +3833,7 @@ dependencies = [ "connection-string", "crosstarget-utils", "either", + "enumflags2", "expect-test", "futures", "getrandom 0.2.11", diff --git a/libs/crosstarget-utils/Cargo.toml b/libs/crosstarget-utils/Cargo.toml index 81bbf540c69..6d1f2c6f7df 100644 --- a/libs/crosstarget-utils/Cargo.toml +++ b/libs/crosstarget-utils/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] futures = "0.3" +enumflags2.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys.workspace = true diff --git a/libs/crosstarget-utils/src/common.rs b/libs/crosstarget-utils/src/common.rs index 5337fa00760..53d5eb84bcc 100644 --- a/libs/crosstarget-utils/src/common.rs +++ b/libs/crosstarget-utils/src/common.rs @@ -27,18 +27,28 @@ pub struct RegExpError { pub message: String, } -#[derive(PartialEq)] +impl Display for RegExpError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Regular expression error: {}", self.message) + } +} + +impl std::error::Error for RegExpError {} + +/// Test-relevant connector capabilities. +#[enumflags2::bitflags] +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] pub enum RegExpFlags { - IgnoreCase, - Multiline, + IgnoreCase = 0b0001, + Multiline = 0b0010, } -impl From for String { - fn from(flags: RegExpFlags) -> Self { - match flags { - RegExpFlags::IgnoreCase => "i", - RegExpFlags::Multiline => "m", +impl RegExpFlags { + pub fn as_str(&self) -> &'static str { + match self { + Self::IgnoreCase => "i", + Self::Multiline => "m", } - .to_string() } } diff --git a/libs/crosstarget-utils/src/native/regex.rs b/libs/crosstarget-utils/src/native/regex.rs index 0a93d4db104..c3c4ee33b1d 100644 --- a/libs/crosstarget-utils/src/native/regex.rs +++ b/libs/crosstarget-utils/src/native/regex.rs @@ -1,3 +1,4 @@ +use enumflags2::BitFlags; use regex::{Regex as NativeRegex, RegexBuilder}; use crate::common::{RegExpError, RegExpFlags}; @@ -7,14 +8,14 @@ pub struct RegExp { } impl RegExp { - pub fn new(pattern: &str, flags: Vec) -> Result { + pub fn new(pattern: &str, flags: BitFlags) -> Result { let mut builder = RegexBuilder::new(pattern); - if flags.contains(&RegExpFlags::Multiline) { + if flags.contains(RegExpFlags::Multiline) { builder.multi_line(true); } - if flags.contains(&RegExpFlags::IgnoreCase) { + if flags.contains(RegExpFlags::IgnoreCase) { builder.case_insensitive(true); } @@ -30,7 +31,7 @@ impl RegExp { self.inner.captures(message).map(|captures| { captures .iter() - .flat_map(|capture| capture.map(|cap| cap.as_str().to_string())) + .flat_map(|capture| capture.map(|cap| cap.as_str().to_owned())) .collect() }) } diff --git a/libs/crosstarget-utils/src/wasm/regex.rs b/libs/crosstarget-utils/src/wasm/regex.rs index 67d0a0eb4c1..457d4f9082b 100644 --- a/libs/crosstarget-utils/src/wasm/regex.rs +++ b/libs/crosstarget-utils/src/wasm/regex.rs @@ -1,3 +1,4 @@ +use enumflags2::BitFlags; use js_sys::RegExp as JSRegExp; use crate::common::{RegExpError, RegExpFlags}; @@ -7,14 +8,14 @@ pub struct RegExp { } impl RegExp { - pub fn new(pattern: &str, flags: Vec) -> Result { - let mut flags_as_str: String = flags.into_iter().map(|flag| String::from(flag)).collect(); + pub fn new(pattern: &str, flags: BitFlags) -> Result { + let mut flags: String = flags.into_iter().map(|flag| flag.as_str()).collect(); // Global flag is implied in `regex::Regex`, so we match that behavior for consistency. - flags_as_str.push('g'); + flags.push('g'); Ok(Self { - inner: JSRegExp::new(pattern, &flags_as_str), + inner: JSRegExp::new(pattern, &flags), }) } @@ -26,7 +27,12 @@ impl RegExp { matches.map(|matches| { let mut captures = Vec::new(); for i in 0..matches.length() { - captures.push(matches.get(i).as_string().unwrap()); + let match_value = matches.get(i); + + // `match_value` may be `undefined`. + if match_value.is_string() { + captures.push(match_value.as_string().unwrap()); + } } captures }) diff --git a/quaint/Cargo.toml b/quaint/Cargo.toml index 42e40d537fd..ff95dab0a87 100644 --- a/quaint/Cargo.toml +++ b/quaint/Cargo.toml @@ -82,6 +82,7 @@ url.workspace = true hex = "0.4" itertools.workspace = true regex.workspace = true +enumflags2.workspace = true either = { version = "1.6" } base64 = { version = "0.12.3" } diff --git a/quaint/src/connector/postgres/error.rs b/quaint/src/connector/postgres/error.rs index 76f3ad132be..66198063672 100644 --- a/quaint/src/connector/postgres/error.rs +++ b/quaint/src/connector/postgres/error.rs @@ -1,4 +1,5 @@ use crosstarget_utils::regex::RegExp; +use enumflags2::BitFlags; use std::fmt::{Display, Formatter}; use crate::error::{DatabaseConstraint, Error, ErrorKind, Name}; @@ -30,7 +31,7 @@ impl Display for PostgresError { } fn extract_fk_constraint_name(message: &str) -> Option { - let re = RegExp::new(r#"foreign key constraint "([^"]+)""#, vec![]).unwrap(); + let re = RegExp::new(r#"foreign key constraint "([^"]+)""#, BitFlags::empty()).unwrap(); re.captures(message).and_then(|caps| caps.get(1).cloned()) } From 25e84bcf793c09c57206c15c8c2934d1e618cd14 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Mon, 14 Oct 2024 21:50:48 +0400 Subject: [PATCH 06/12] feat(crosstarget-utils): introduce common trait "RegExprCompact" for "regex" util --- libs/crosstarget-utils/src/common/mod.rs | 3 ++ .../src/{common.rs => common/regex.rs} | 32 ++++++------------- libs/crosstarget-utils/src/common/spawn.rs | 12 +++++++ libs/crosstarget-utils/src/common/timeout.rs | 12 +++++++ libs/crosstarget-utils/src/lib.rs | 3 +- libs/crosstarget-utils/src/native/regex.rs | 12 +++---- libs/crosstarget-utils/src/native/spawn.rs | 2 +- libs/crosstarget-utils/src/native/time.rs | 2 +- libs/crosstarget-utils/src/wasm/regex.rs | 12 +++---- libs/crosstarget-utils/src/wasm/spawn.rs | 2 +- libs/crosstarget-utils/src/wasm/time.rs | 2 +- quaint/src/connector/postgres/error.rs | 2 +- 12 files changed, 54 insertions(+), 42 deletions(-) create mode 100644 libs/crosstarget-utils/src/common/mod.rs rename libs/crosstarget-utils/src/{common.rs => common/regex.rs} (58%) create mode 100644 libs/crosstarget-utils/src/common/spawn.rs create mode 100644 libs/crosstarget-utils/src/common/timeout.rs diff --git a/libs/crosstarget-utils/src/common/mod.rs b/libs/crosstarget-utils/src/common/mod.rs new file mode 100644 index 00000000000..d1701cc3408 --- /dev/null +++ b/libs/crosstarget-utils/src/common/mod.rs @@ -0,0 +1,3 @@ +pub mod regex; +pub mod spawn; +pub mod timeout; diff --git a/libs/crosstarget-utils/src/common.rs b/libs/crosstarget-utils/src/common/regex.rs similarity index 58% rename from libs/crosstarget-utils/src/common.rs rename to libs/crosstarget-utils/src/common/regex.rs index 53d5eb84bcc..074024622af 100644 --- a/libs/crosstarget-utils/src/common.rs +++ b/libs/crosstarget-utils/src/common/regex.rs @@ -1,27 +1,5 @@ use std::fmt::Display; -#[derive(Debug)] -pub struct SpawnError; - -impl Display for SpawnError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Failed to spawn a future") - } -} - -impl std::error::Error for SpawnError {} - -#[derive(Debug)] -pub struct TimeoutError; - -impl Display for TimeoutError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Operation timed out") - } -} - -impl std::error::Error for TimeoutError {} - #[derive(Debug)] pub struct RegExpError { pub message: String, @@ -52,3 +30,13 @@ impl RegExpFlags { } } } + +pub trait RegExpCompat { + /// Searches for the first match of this regex in the haystack given, and if found, + /// returns not only the overall match but also the matches of each capture group in the regex. + /// If no match is found, then None is returned. + fn captures(&self, message: &str) -> Option>; + + /// Tests if the regex matches the input string. + fn test(&self, message: &str) -> bool; +} diff --git a/libs/crosstarget-utils/src/common/spawn.rs b/libs/crosstarget-utils/src/common/spawn.rs new file mode 100644 index 00000000000..7a3e9582ee7 --- /dev/null +++ b/libs/crosstarget-utils/src/common/spawn.rs @@ -0,0 +1,12 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub struct SpawnError; + +impl Display for SpawnError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to spawn a future") + } +} + +impl std::error::Error for SpawnError {} diff --git a/libs/crosstarget-utils/src/common/timeout.rs b/libs/crosstarget-utils/src/common/timeout.rs new file mode 100644 index 00000000000..0a7cb05b303 --- /dev/null +++ b/libs/crosstarget-utils/src/common/timeout.rs @@ -0,0 +1,12 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub struct TimeoutError; + +impl Display for TimeoutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Operation timed out") + } +} + +impl std::error::Error for TimeoutError {} diff --git a/libs/crosstarget-utils/src/lib.rs b/libs/crosstarget-utils/src/lib.rs index a41d8dd0f9a..1cfa25edeed 100644 --- a/libs/crosstarget-utils/src/lib.rs +++ b/libs/crosstarget-utils/src/lib.rs @@ -9,4 +9,5 @@ mod native; #[cfg(not(target_arch = "wasm32"))] pub use crate::native::*; -pub use common::SpawnError; +pub use crate::common::regex::RegExpCompat; +pub use crate::common::spawn::SpawnError; diff --git a/libs/crosstarget-utils/src/native/regex.rs b/libs/crosstarget-utils/src/native/regex.rs index c3c4ee33b1d..59fe88899c6 100644 --- a/libs/crosstarget-utils/src/native/regex.rs +++ b/libs/crosstarget-utils/src/native/regex.rs @@ -1,7 +1,7 @@ use enumflags2::BitFlags; use regex::{Regex as NativeRegex, RegexBuilder}; -use crate::common::{RegExpError, RegExpFlags}; +use crate::common::regex::{RegExpCompat, RegExpError, RegExpFlags}; pub struct RegExp { inner: NativeRegex, @@ -23,11 +23,10 @@ impl RegExp { Ok(Self { inner }) } +} - /// Searches for the first match of this regex in the haystack given, and if found, - /// returns not only the overall match but also the matches of each capture group in the regex. - /// If no match is found, then None is returned. - pub fn captures(&self, message: &str) -> Option> { +impl RegExpCompat for RegExp { + fn captures(&self, message: &str) -> Option> { self.inner.captures(message).map(|captures| { captures .iter() @@ -36,8 +35,7 @@ impl RegExp { }) } - /// Tests if the regex matches the input string. - pub fn test(&self, message: &str) -> bool { + fn test(&self, message: &str) -> bool { self.inner.is_match(message) } } diff --git a/libs/crosstarget-utils/src/native/spawn.rs b/libs/crosstarget-utils/src/native/spawn.rs index 70e4c3708f2..31971aa47c4 100644 --- a/libs/crosstarget-utils/src/native/spawn.rs +++ b/libs/crosstarget-utils/src/native/spawn.rs @@ -1,7 +1,7 @@ use futures::TryFutureExt; use std::future::Future; -use crate::common::SpawnError; +use crate::common::spawn::SpawnError; pub fn spawn_if_possible(future: F) -> impl Future> where diff --git a/libs/crosstarget-utils/src/native/time.rs b/libs/crosstarget-utils/src/native/time.rs index 3b154a27565..9c92272cdbc 100644 --- a/libs/crosstarget-utils/src/native/time.rs +++ b/libs/crosstarget-utils/src/native/time.rs @@ -3,7 +3,7 @@ use std::{ time::{Duration, Instant}, }; -use crate::common::TimeoutError; +use crate::common::timeout::TimeoutError; pub struct ElapsedTimeCounter { instant: Instant, diff --git a/libs/crosstarget-utils/src/wasm/regex.rs b/libs/crosstarget-utils/src/wasm/regex.rs index 457d4f9082b..1865befb80a 100644 --- a/libs/crosstarget-utils/src/wasm/regex.rs +++ b/libs/crosstarget-utils/src/wasm/regex.rs @@ -1,7 +1,7 @@ use enumflags2::BitFlags; use js_sys::RegExp as JSRegExp; -use crate::common::{RegExpError, RegExpFlags}; +use crate::common::regex::{RegExpCompat, RegExpError, RegExpFlags}; pub struct RegExp { inner: JSRegExp, @@ -18,11 +18,10 @@ impl RegExp { inner: JSRegExp::new(pattern, &flags), }) } +} - /// Searches for the first match of this regex in the haystack given, and if found, - /// returns not only the overall match but also the matches of each capture group in the regex. - /// If no match is found, then None is returned. - pub fn captures(&self, message: &str) -> Option> { +impl RegExpCompat for RegExp { + fn captures(&self, message: &str) -> Option> { let matches = self.inner.exec(message); matches.map(|matches| { let mut captures = Vec::new(); @@ -38,8 +37,7 @@ impl RegExp { }) } - /// Tests if the regex matches the input string. - pub fn test(&self, input: &str) -> bool { + fn test(&self, input: &str) -> bool { self.inner.test(input) } } diff --git a/libs/crosstarget-utils/src/wasm/spawn.rs b/libs/crosstarget-utils/src/wasm/spawn.rs index e27104c3b94..f9700d8a007 100644 --- a/libs/crosstarget-utils/src/wasm/spawn.rs +++ b/libs/crosstarget-utils/src/wasm/spawn.rs @@ -4,7 +4,7 @@ use futures::TryFutureExt; use tokio::sync::oneshot; use wasm_bindgen_futures::spawn_local; -use crate::common::SpawnError; +use crate::common::spawn::SpawnError; pub fn spawn_if_possible(future: F) -> impl Future> where diff --git a/libs/crosstarget-utils/src/wasm/time.rs b/libs/crosstarget-utils/src/wasm/time.rs index 18f3394b746..f8cbe189e3b 100644 --- a/libs/crosstarget-utils/src/wasm/time.rs +++ b/libs/crosstarget-utils/src/wasm/time.rs @@ -7,7 +7,7 @@ use std::time::Duration; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; -use crate::common::TimeoutError; +use crate::common::timeout::TimeoutError; #[wasm_bindgen] extern "C" { diff --git a/quaint/src/connector/postgres/error.rs b/quaint/src/connector/postgres/error.rs index 66198063672..0f19cf99e13 100644 --- a/quaint/src/connector/postgres/error.rs +++ b/quaint/src/connector/postgres/error.rs @@ -1,4 +1,4 @@ -use crosstarget_utils::regex::RegExp; +use crosstarget_utils::{regex::RegExp, RegExpCompat}; use enumflags2::BitFlags; use std::fmt::{Display, Formatter}; From da44ead5035767f73a9ef5d806ae94ebccbf0482 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Mon, 14 Oct 2024 21:52:26 +0400 Subject: [PATCH 07/12] chore(crosstarget-utils): rewrite comment --- libs/crosstarget-utils/src/common/regex.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/crosstarget-utils/src/common/regex.rs b/libs/crosstarget-utils/src/common/regex.rs index 074024622af..d488cfec60b 100644 --- a/libs/crosstarget-utils/src/common/regex.rs +++ b/libs/crosstarget-utils/src/common/regex.rs @@ -13,7 +13,7 @@ impl Display for RegExpError { impl std::error::Error for RegExpError {} -/// Test-relevant connector capabilities. +/// Flag modifiers for regular expressions. #[enumflags2::bitflags] #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] From 6d8e6c5ad25b59dc5d149875efb14d8b07537822 Mon Sep 17 00:00:00 2001 From: Alberto Schiabel Date: Tue, 15 Oct 2024 11:41:46 +0400 Subject: [PATCH 08/12] chore(review): use derive_more Display Co-authored-by: Alexey Orlenko --- libs/crosstarget-utils/src/common/regex.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/libs/crosstarget-utils/src/common/regex.rs b/libs/crosstarget-utils/src/common/regex.rs index d488cfec60b..51542fb0deb 100644 --- a/libs/crosstarget-utils/src/common/regex.rs +++ b/libs/crosstarget-utils/src/common/regex.rs @@ -1,16 +1,11 @@ -use std::fmt::Display; +use derive_more::Display; -#[derive(Debug)] +#[derive(Debug, Display)] +#[display("Regular expression error: {message}")] pub struct RegExpError { pub message: String, } -impl Display for RegExpError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Regular expression error: {}", self.message) - } -} - impl std::error::Error for RegExpError {} /// Flag modifiers for regular expressions. From c5a6135c0512d6e5fa94ba20dd776b17b853c937 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Tue, 15 Oct 2024 11:49:32 +0400 Subject: [PATCH 09/12] fix(crosstarget-utils): don't change the number of captured groups in regex --- libs/crosstarget-utils/src/wasm/regex.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/crosstarget-utils/src/wasm/regex.rs b/libs/crosstarget-utils/src/wasm/regex.rs index 1865befb80a..c792ec410d9 100644 --- a/libs/crosstarget-utils/src/wasm/regex.rs +++ b/libs/crosstarget-utils/src/wasm/regex.rs @@ -28,9 +28,12 @@ impl RegExpCompat for RegExp { for i in 0..matches.length() { let match_value = matches.get(i); - // `match_value` may be `undefined`. + // We keep the same number of captures as the number of groups in the regex pattern, + // but we guarantee that the captures are always strings. if match_value.is_string() { captures.push(match_value.as_string().unwrap()); + } else { + captures.push(String::new()); } } captures From 6e54f4c1ea9561a0c5b5e17f4aa0f6643f2f9330 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Tue, 15 Oct 2024 12:09:16 +0400 Subject: [PATCH 10/12] fix(crosstarget-utils): fixes on regex and derive_more --- Cargo.lock | 1 + Cargo.toml | 1 + libs/crosstarget-utils/Cargo.toml | 3 ++- libs/crosstarget-utils/src/common/regex.rs | 2 +- libs/crosstarget-utils/src/wasm/regex.rs | 9 +++------ .../connectors/mongodb-query-connector/Cargo.toml | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dee32574c6..7c4c52dda16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -923,6 +923,7 @@ dependencies = [ name = "crosstarget-utils" version = "0.1.0" dependencies = [ + "derive_more", "enumflags2", "futures", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index f2000ba619d..930f95e0c8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ tokio = { version = "1", features = [ "time", ] } chrono = { version = "0.4.38", features = ["serde"] } +derive_more = "0.99.17" user-facing-errors = { path = "./libs/user-facing-errors" } uuid = { version = "1", features = ["serde", "v4", "v7", "js"] } indoc = "2.0.1" diff --git a/libs/crosstarget-utils/Cargo.toml b/libs/crosstarget-utils/Cargo.toml index 6d1f2c6f7df..8dd311e292d 100644 --- a/libs/crosstarget-utils/Cargo.toml +++ b/libs/crosstarget-utils/Cargo.toml @@ -6,8 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -futures = "0.3" +derive_more.workspace = true enumflags2.workspace = true +futures = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys.workspace = true diff --git a/libs/crosstarget-utils/src/common/regex.rs b/libs/crosstarget-utils/src/common/regex.rs index 51542fb0deb..825d3e85d6c 100644 --- a/libs/crosstarget-utils/src/common/regex.rs +++ b/libs/crosstarget-utils/src/common/regex.rs @@ -1,7 +1,7 @@ use derive_more::Display; #[derive(Debug, Display)] -#[display("Regular expression error: {message}")] +#[display(fmt = "Regular expression error: {message}")] pub struct RegExpError { pub message: String, } diff --git a/libs/crosstarget-utils/src/wasm/regex.rs b/libs/crosstarget-utils/src/wasm/regex.rs index c792ec410d9..4ca60b82721 100644 --- a/libs/crosstarget-utils/src/wasm/regex.rs +++ b/libs/crosstarget-utils/src/wasm/regex.rs @@ -24,17 +24,14 @@ impl RegExpCompat for RegExp { fn captures(&self, message: &str) -> Option> { let matches = self.inner.exec(message); matches.map(|matches| { - let mut captures = Vec::new(); + let mut captures: Vec = Vec::new(); for i in 0..matches.length() { let match_value = matches.get(i); // We keep the same number of captures as the number of groups in the regex pattern, // but we guarantee that the captures are always strings. - if match_value.is_string() { - captures.push(match_value.as_string().unwrap()); - } else { - captures.push(String::new()); - } + let capture: String = match_value.try_into().ok().unwrap_or_default(); + captures.push(capture); } captures }) diff --git a/query-engine/connectors/mongodb-query-connector/Cargo.toml b/query-engine/connectors/mongodb-query-connector/Cargo.toml index e66ca420402..386c694b267 100644 --- a/query-engine/connectors/mongodb-query-connector/Cargo.toml +++ b/query-engine/connectors/mongodb-query-connector/Cargo.toml @@ -22,7 +22,7 @@ uuid.workspace = true indexmap.workspace = true query-engine-metrics = { path = "../../metrics" } cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" } -derive_more = "0.99.17" +derive_more.workspace = true [dependencies.query-structure] path = "../../query-structure" From 655095f60e574d803ce2d16b1e2d64a0344b2f42 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Tue, 15 Oct 2024 12:25:47 +0400 Subject: [PATCH 11/12] chore: apply nits --- libs/crosstarget-utils/src/wasm/regex.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/libs/crosstarget-utils/src/wasm/regex.rs b/libs/crosstarget-utils/src/wasm/regex.rs index 4ca60b82721..500f631282e 100644 --- a/libs/crosstarget-utils/src/wasm/regex.rs +++ b/libs/crosstarget-utils/src/wasm/regex.rs @@ -22,18 +22,13 @@ impl RegExp { impl RegExpCompat for RegExp { fn captures(&self, message: &str) -> Option> { - let matches = self.inner.exec(message); - matches.map(|matches| { - let mut captures: Vec = Vec::new(); - for i in 0..matches.length() { - let match_value = matches.get(i); - - // We keep the same number of captures as the number of groups in the regex pattern, - // but we guarantee that the captures are always strings. - let capture: String = match_value.try_into().ok().unwrap_or_default(); - captures.push(capture); - } - captures + self.inner.exec(message).map(|matches| { + // We keep the same number of captures as the number of groups in the regex pattern, + // but we guarantee that the captures are always strings. + matches + .iter() + .map(|match_value| match_value.try_into().ok().unwrap_or_default()) + .collect() }) } From c1cc43c3e929bda77193a9fe4f94731a859ef4f8 Mon Sep 17 00:00:00 2001 From: jkomyno Date: Tue, 15 Oct 2024 12:29:37 +0400 Subject: [PATCH 12/12] feat(crosstarget-utils): replace every manual "Display" instance with "derive_more" --- libs/crosstarget-utils/src/common/spawn.rs | 12 ++++-------- libs/crosstarget-utils/src/common/timeout.rs | 11 +++-------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/libs/crosstarget-utils/src/common/spawn.rs b/libs/crosstarget-utils/src/common/spawn.rs index 7a3e9582ee7..77560452dbd 100644 --- a/libs/crosstarget-utils/src/common/spawn.rs +++ b/libs/crosstarget-utils/src/common/spawn.rs @@ -1,12 +1,8 @@ -use std::fmt::Display; +use derive_more::Display; -#[derive(Debug)] -pub struct SpawnError; +#[derive(Debug, Display)] +#[display(fmt = "Failed to spawn a future")] -impl Display for SpawnError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Failed to spawn a future") - } -} +pub struct SpawnError; impl std::error::Error for SpawnError {} diff --git a/libs/crosstarget-utils/src/common/timeout.rs b/libs/crosstarget-utils/src/common/timeout.rs index 0a7cb05b303..829abaf0ec0 100644 --- a/libs/crosstarget-utils/src/common/timeout.rs +++ b/libs/crosstarget-utils/src/common/timeout.rs @@ -1,12 +1,7 @@ -use std::fmt::Display; +use derive_more::Display; -#[derive(Debug)] +#[derive(Debug, Display)] +#[display(fmt = "Operation timed out")] pub struct TimeoutError; -impl Display for TimeoutError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Operation timed out") - } -} - impl std::error::Error for TimeoutError {}