Skip to content

Commit

Permalink
Merge pull request #77 from aumetra/js-sys-regex
Browse files Browse the repository at this point in the history
Use `js-sys` regex type on wasm32-unknown-unknown with feature enabled
  • Loading branch information
jprochazk authored Nov 22, 2023
2 parents cedd6a1 + 0757e9f commit 09e465a
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[alias]
xtask = "run --quiet --manifest-path ./xtask/Cargo.toml --"
x = "run --quiet --manifest-path ./xtask/Cargo.toml --"

[target.wasm32-unknown-unknown]
runner = 'wasm-bindgen-test-runner'
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@ permissions:
contents: read

jobs:
test-wasm:
name: Test WASM
runs-on: ubuntu-20.04

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
targets: wasm32-unknown-unknown

- name: Cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "rust-stable-test"

- name: Install wasm-bindgen-cli
run: cargo install --locked wasm-bindgen-cli

- name: Build xtask
run: cargo build --manifest-path ./xtask/Cargo.toml

- name: Run tests
run: cargo x test --wasm

test:
name: Test
runs-on: ${{ matrix.os }}
Expand Down
8 changes: 8 additions & 0 deletions garde/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ email = ["regex"]
email-idna = ["dep:idna"]
regex = ["dep:regex", "dep:once_cell", "garde_derive?/regex"]
pattern = ["regex"] # for backward compatibility with <0.14.0
js-sys = ["dep:js-sys"]

[dependencies]
garde_derive = { version = "0.16.0", path = "../garde_derive", optional = true, default-features = false }
Expand All @@ -47,12 +48,19 @@ regex = { version = "1", default-features = false, features = [
once_cell = { version = "1", optional = true }
idna = { version = "0.3", optional = true }

[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
js-sys = { version = "0.3", optional = true }

[dev-dependencies]
trybuild = { version = "1.0" }
insta = { version = "1.29" }
owo-colors = { version = "3.5.0" }
glob = "0.3.1"

[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies]
wasm-bindgen-test = "0.3.38"

[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies]
criterion = "0.4"

[[bench]]
Expand Down
29 changes: 21 additions & 8 deletions garde/src/rules/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@
use std::fmt::Display;
use std::str::FromStr;

use once_cell::sync::Lazy;
use regex::Regex;

use super::pattern::Matcher;
use super::AsStr;
use crate::error::Error;

macro_rules! init_regex {
($var:ident => $p:literal) => {
#[cfg(not(all(feature = "js-sys", target_arch = "wasm32", target_os = "unknown")))]
static $var: $crate::rules::pattern::regex::StaticPattern =
$crate::rules::pattern::regex::init_pattern!($p);

#[cfg(all(feature = "js-sys", target_arch = "wasm32", target_os = "unknown"))]
static $var: $crate::rules::pattern::regex_js_sys::StaticPattern =
$crate::rules::pattern::regex_js_sys::init_pattern!($p);
};
}

pub fn apply<T: Email>(v: &T, _: ()) -> Result<(), Error> {
if let Err(e) = v.validate_email() {
return Err(Error::new(format!("not a valid email: {e}")));
Expand Down Expand Up @@ -90,8 +100,11 @@ pub fn parse_email(s: &str) -> Result<(), InvalidEmail> {
if user.len() > 64 {
return Err(InvalidEmail::UserLengthExceeded);
}
static USER_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i-u)^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+\z").unwrap());

init_regex! {
USER_RE => r"(?i-u)^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+\z"
}

if !USER_RE.is_match(user) {
return Err(InvalidEmail::InvalidUser);
}
Expand Down Expand Up @@ -123,9 +136,9 @@ pub fn parse_email(s: &str) -> Result<(), InvalidEmail> {
}

fn is_valid_domain(domain: &str) -> bool {
static DOMAIN_NAME_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i-u)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$").unwrap()
});
init_regex! {
DOMAIN_NAME_RE => r"(?i-u)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$"
};

if DOMAIN_NAME_RE.is_match(domain) {
return true;
Expand Down
62 changes: 62 additions & 0 deletions garde/src/rules/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,68 @@ impl<T: Pattern> Pattern for Option<T> {
}
}

#[cfg(all(
feature = "regex",
feature = "js-sys",
target_arch = "wasm32",
target_os = "unknown"
))]
#[doc(hidden)]
pub mod regex_js_sys {
pub use ::js_sys::RegExp;

use super::*;

impl Matcher for RegExp {
fn is_match(&self, haystack: &str) -> bool {
self.test(haystack)
}
}

impl AsStr for RegExp {
fn as_str(&self) -> &str {
"[Not supported in JS]"
}
}

pub struct SyncWrapper<T>(T);

impl<T> SyncWrapper<T> {
/// Safety: You have to ensure that this value is never shared or sent between threads unless the inner value supports it
pub const unsafe fn new(inner: T) -> Self {
Self(inner)
}
}

impl<T: AsStr> AsStr for SyncWrapper<T> {
fn as_str(&self) -> &str {
self.0.as_str()
}
}

impl<T: Matcher> Matcher for SyncWrapper<T> {
fn is_match(&self, haystack: &str) -> bool {
self.0.is_match(haystack)
}
}

unsafe impl<T> Send for SyncWrapper<T> {}
unsafe impl<T> Sync for SyncWrapper<T> {}

pub type StaticPattern = once_cell::sync::Lazy<SyncWrapper<RegExp>>;

#[macro_export]
macro_rules! __init_js_sys_pattern {
($pat:literal) => {
$crate::rules::pattern::regex_js_sys::StaticPattern::new(|| {
// Safety: `wasm32-unknown-unknown` is inherently single-threaded. Therefore `Send` and `Sync` aren't really relevant
unsafe { $crate::rules::pattern::regex_js_sys::SyncWrapper::new(::js_sys::RegExp::new($pat, "u")) }
})
};
}
pub use crate::__init_js_sys_pattern as init_pattern;
}

#[cfg(feature = "regex")]
#[doc(hidden)]
pub mod regex {
Expand Down
16 changes: 14 additions & 2 deletions garde/tests/rules/pattern.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use once_cell::sync::Lazy;
use regex::Regex;

use super::util;

mod sub {
use once_cell::sync::Lazy;

use super::*;

pub static LAZY_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^abcd|efgh$").unwrap());
Expand All @@ -24,11 +25,21 @@ struct Test<'a> {
inner: &'a [&'a str],
}

#[cfg(not(all(feature = "js-sys", target_arch = "wasm32", target_os = "unknown")))]
fn create_regex() -> Regex {
Regex::new(r"^abcd|efgh$").unwrap()
}

#[test]
#[cfg(all(feature = "js-sys", target_arch = "wasm32", target_os = "unknown"))]
fn create_regex() -> ::js_sys::RegExp {
::js_sys::RegExp::new(r"^abcd|efgh$", "u")
}

#[cfg_attr(not(all(target_arch = "wasm32", target_os = "unknown")), test)]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn pattern_valid() {
util::check_ok(
&[
Expand All @@ -49,6 +60,7 @@ fn pattern_valid() {
)
}

#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[test]
fn pattern_invalid() {
util::check_fail!(
Expand Down
14 changes: 14 additions & 0 deletions garde_derive/src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,22 @@ impl<'a> ToTokens for Rules<'a> {
Pattern(pat) => match pat {
model::ValidatePattern::Expr(expr) => quote_spanned!(expr.span() => (&#expr,)),
model::ValidatePattern::Lit(s) => quote!({
#[cfg(not(all(
feature = "js-sys",
target_arch = "wasm32",
target_os = "unknown"
)))]
static PATTERN: ::garde::rules::pattern::regex::StaticPattern =
::garde::rules::pattern::regex::init_pattern!(#s);

#[cfg(all(
feature = "js-sys",
target_arch = "wasm32",
target_os = "unknown"
))]
static PATTERN: ::garde::rules::pattern::regex_js_sys::StaticPattern =
::garde::rules::pattern::regex_js_sys::init_pattern!(#s);

(&PATTERN,)
}),
},
Expand Down
15 changes: 14 additions & 1 deletion xtask/src/task/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ pub struct Test {
targets: Vec<Target>,
#[argp(switch, description = "Run insta with --review")]
review: bool,
#[argp(
switch,
description = "Run the tests for the `wasm32-unknown-unknown` platform"
)]
wasm: bool,
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -57,7 +62,9 @@ impl argp::FromArgValue for Target {
impl Test {
pub fn run(mut self) -> Result {
let review = self.review;
let commands = if self.targets.is_empty() {
let mut commands = if self.targets.is_empty() && self.wasm {
vec![unit(), ui(review), rules(review)]
} else if self.targets.is_empty() {
vec![unit(), ui(review), rules(review), axum()]
} else {
self.targets.sort();
Expand All @@ -73,6 +80,12 @@ impl Test {
.collect()
};

if self.wasm {
commands.iter_mut().for_each(|cmd| {
cmd.args(["--target", "wasm32-unknown-unknown"]);
});
}

for command in commands {
command.run()?;
}
Expand Down

0 comments on commit 09e465a

Please sign in to comment.