diff --git a/Cargo.lock b/Cargo.lock index 1f0159a2c9..7ada4b5f09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6223,15 +6223,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "perry-ext-validator" -version = "0.5.1598" -dependencies = [ - "perry-ffi", - "perry-validation", - "serde_json", -] - [[package]] name = "perry-ext-ws" version = "0.5.1598" @@ -6426,7 +6417,6 @@ dependencies = [ "perry-ffi", "perry-runtime", "perry-updater", - "perry-validation", "proptest", "rand 0.10.2", "rand_core 0.6.4", @@ -6679,16 +6669,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "perry-validation" -version = "0.5.1598" -dependencies = [ - "idna", - "regex", - "url", - "validator", -] - [[package]] name = "perry-wasm-host" version = "0.5.1598" @@ -10031,20 +10011,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "validator" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d68c6633c483df6780cc5277a417c7c2d1bceee2649d06c8ab6b0fd2dd3c81" -dependencies = [ - "idna", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", -] - [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index aaee929951..db29dc5e89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,6 @@ members = [ "crates/perry-ext-uuid", "crates/perry-ext-bcrypt", "crates/perry-ext-argon2", - "crates/perry-ext-validator", - "crates/perry-validation", "crates/perry-perex", "crates/perry-ext-lru-cache", "crates/perry-ext-better-sqlite3", @@ -481,8 +479,6 @@ perry-ext-nanoid = { path = "crates/perry-ext-nanoid" } perry-ext-uuid = { path = "crates/perry-ext-uuid" } perry-ext-bcrypt = { path = "crates/perry-ext-bcrypt" } perry-ext-argon2 = { path = "crates/perry-ext-argon2" } -perry-ext-validator = { path = "crates/perry-ext-validator" } -perry-validation = { path = "crates/perry-validation" } perry-perex = { path = "crates/perry-perex" } perry-ext-lru-cache = { path = "crates/perry-ext-lru-cache" } perry-ext-better-sqlite3 = { path = "crates/perry-ext-better-sqlite3" } diff --git a/changelog.d/10690-validator-native-binding-removal.md b/changelog.d/10690-validator-native-binding-removal.md new file mode 100644 index 0000000000..8429de8e4a --- /dev/null +++ b/changelog.d/10690-validator-native-binding-removal.md @@ -0,0 +1,17 @@ +Removed the native `validator` binding: 9+ methods threw "not implemented" +(`trim`/`contains`/`equals`/`isAlpha`/`escape`/`isMobilePhone`/etc.), `trim()` +silently returned `undefined`, and the 5 implemented checks +(`isEmail`/`isURL`/`isUUID`/`isJSON`/`isEmpty`) returned `0`/`1` instead of +real booleans. `import validator from "validator"` (no +`perry.compilePackages` entry) now compiles the real npm package from +source, matching Node for all 50 checks. + +Deleted both duplicate hand-written implementations +(`crates/perry-ext-validator` and `crates/perry-stdlib/src/validator.rs`, +which independently exported the same `js_validator_*` symbols — #10678) +plus `crates/perry-validation`, a shared grammar-helper crate consumed only +by the two duplicates. Also fixed a standalone-workspace release fixture +(`tests/release/packages/next-app-route/provider/stdlib/Cargo.toml`) that +referenced the now-deleted `validation` feature — it has its own +`Cargo.lock` and isn't a member of the main workspace, so `cargo check +--workspace` never covers it. diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index de7f6a8bec..d0d365f8d3 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -48,7 +48,6 @@ pub const NATIVE_MODULES: &[&str] = &[ "dotenv", // .env file loader "dotenv/config", // dotenv's auto-load-on-import subpath "nanoid", // compact URL-safe ID generation - "validator", // string validators/sanitizers "ethers", // Ethereum library (utils/wallet/ABI) "mongodb", // MongoDB driver "better-sqlite3", // synchronous SQLite (replaces the N-API addon) diff --git a/crates/perry-api-manifest/src/entries/part_1.rs b/crates/perry-api-manifest/src/entries/part_1.rs index 5af3539565..bc68f1b035 100644 --- a/crates/perry-api-manifest/src/entries/part_1.rs +++ b/crates/perry-api-manifest/src/entries/part_1.rs @@ -1210,66 +1210,6 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[ }], TypeSpec::String, ), - method_sig( - "validator", - "isEmail", - false, - None, - &[ParamSpec::Named { - name: "s", - ty: TypeSpec::String, - optional: false, - }], - TypeSpec::Bool, - ), - method_sig( - "validator", - "isURL", - false, - None, - &[ParamSpec::Named { - name: "s", - ty: TypeSpec::String, - optional: false, - }], - TypeSpec::Bool, - ), - method_sig( - "validator", - "isUUID", - false, - None, - &[ParamSpec::Named { - name: "s", - ty: TypeSpec::String, - optional: false, - }], - TypeSpec::Bool, - ), - method_sig( - "validator", - "isJSON", - false, - None, - &[ParamSpec::Named { - name: "s", - ty: TypeSpec::String, - optional: false, - }], - TypeSpec::Bool, - ), - method_sig( - "validator", - "isEmpty", - false, - None, - &[ParamSpec::Named { - name: "s", - ty: TypeSpec::String, - optional: false, - }], - TypeSpec::Bool, - ), // #4917 — real retry semantics: options (numOfAttempts/startingDelay/ // timeMultiple/maxDelay/delayFirstAttempt/jitter/retry) honored; // Promise-returning tasks retry on rejection via promise reactions. diff --git a/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs b/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs index f94c677f70..9d73a01b09 100644 --- a/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs +++ b/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs @@ -139,52 +139,6 @@ pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[ args: &[NA_F64], ret: NR_STR, }, - // ========== validator ========== - NativeModSig { - module: "validator", - has_receiver: false, - method: "isEmail", - class_filter: None, - runtime: "js_validator_is_email", - args: &[NA_STR], - ret: NR_F64, - }, - NativeModSig { - module: "validator", - has_receiver: false, - method: "isURL", - class_filter: None, - runtime: "js_validator_is_url", - args: &[NA_STR], - ret: NR_F64, - }, - NativeModSig { - module: "validator", - has_receiver: false, - method: "isUUID", - class_filter: None, - runtime: "js_validator_is_uuid", - args: &[NA_STR], - ret: NR_F64, - }, - NativeModSig { - module: "validator", - has_receiver: false, - method: "isJSON", - class_filter: None, - runtime: "js_validator_is_json", - args: &[NA_STR], - ret: NR_F64, - }, - NativeModSig { - module: "validator", - has_receiver: false, - method: "isEmpty", - class_filter: None, - runtime: "js_validator_is_empty", - args: &[NA_STR], - ret: NR_F64, - }, // ========== exponential-backoff ========== NativeModSig { module: "exponential-backoff", diff --git a/crates/perry-codegen/src/runtime_decls/stdlib_ffi/streams_events.rs b/crates/perry-codegen/src/runtime_decls/stdlib_ffi/streams_events.rs index 3decf0f8cc..a7b765f0ce 100644 --- a/crates/perry-codegen/src/runtime_decls/stdlib_ffi/streams_events.rs +++ b/crates/perry-codegen/src/runtime_decls/stdlib_ffi/streams_events.rs @@ -293,22 +293,4 @@ pub(crate) fn declare_streams_events(module: &mut LlModule) { module.declare_function("js_ratelimit_new_from_options", I64, &[I64]); module.declare_function("js_ratelimit_penalty", I64, &[I64, I64, DOUBLE]); module.declare_function("js_ratelimit_reward", I64, &[I64, I64, DOUBLE]); - - // ========== Validator ========== - module.declare_function("js_validator_contains", DOUBLE, &[I64, I64]); - module.declare_function("js_validator_equals", DOUBLE, &[I64, I64]); - module.declare_function("js_validator_is_alpha", DOUBLE, &[I64]); - module.declare_function("js_validator_is_alphanumeric", DOUBLE, &[I64]); - module.declare_function("js_validator_is_email", DOUBLE, &[I64]); - module.declare_function("js_validator_is_empty", DOUBLE, &[I64]); - module.declare_function("js_validator_is_float", DOUBLE, &[I64]); - module.declare_function("js_validator_is_hexadecimal", DOUBLE, &[I64]); - module.declare_function("js_validator_is_int", DOUBLE, &[I64]); - module.declare_function("js_validator_is_json", DOUBLE, &[I64]); - module.declare_function("js_validator_is_length", DOUBLE, &[I64, DOUBLE, DOUBLE]); - module.declare_function("js_validator_is_lowercase", DOUBLE, &[I64]); - module.declare_function("js_validator_is_numeric", DOUBLE, &[I64]); - module.declare_function("js_validator_is_uppercase", DOUBLE, &[I64]); - module.declare_function("js_validator_is_url", DOUBLE, &[I64]); - module.declare_function("js_validator_is_uuid", DOUBLE, &[I64]); } diff --git a/crates/perry-ext-validator/Cargo.toml b/crates/perry-ext-validator/Cargo.toml deleted file mode 100644 index c841172e26..0000000000 --- a/crates/perry-ext-validator/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "perry-ext-validator" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Native bindings for the npm `validator` package — uses only `perry-ffi`. Sync, string-only port (Phase 5 step 8)." - -[lints] -workspace = true - -[lib] -crate-type = ["staticlib", "rlib"] - -[dependencies] -perry-ffi.workspace = true -perry-validation.workspace = true -serde_json = { workspace = true } - -[dev-dependencies] -perry-ffi = { workspace = true, features = ["runtime-link"] } diff --git a/crates/perry-ext-validator/src/lib.rs b/crates/perry-ext-validator/src/lib.rs deleted file mode 100644 index 263338b564..0000000000 --- a/crates/perry-ext-validator/src/lib.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Native bindings for the npm `validator` package. -//! -//! Sync, string-only — fits the perry-ffi v0.5 surface exactly. -//! Functionally identical to `crates/perry-stdlib/src/validator.rs`. -//! Eighth wrapper port under #466 Phase 5. -//! -//! Booleans cross the FFI as `f64` (`1.0` / `0.0`) per Perry's -//! existing convention for sync FFI booleans — same as the -//! perry-stdlib copy. No new perry-ffi surface needed. - -use perry_ffi::{read_string, JsString, StringHeader}; - -unsafe fn read_str(ptr: *const StringHeader) -> Option<&'static str> { - let handle = JsString::from_raw(ptr as *mut StringHeader); - read_string(handle) -} - -unsafe fn read_string_owned(ptr: *const StringHeader) -> Option { - read_str(ptr).map(String::from) -} - -#[inline] -fn b(v: bool) -> f64 { - if v { - 1.0 - } else { - 0.0 - } -} - -/// `validator.isEmail(str)`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_email(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(perry_validation::is_email(input)) -} - -/// `validator.isURL(str)`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_url(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(perry_validation::is_url(input)) -} - -/// `validator.isUUID(str)`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_uuid(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(perry_validation::is_uuid(input)) -} - -/// `validator.isAlpha(str)`. Empty string is `false`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_alpha(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - if input.is_empty() { - return 0.0; - } - b(input.chars().all(|c| c.is_alphabetic())) -} - -/// `validator.isAlphanumeric(str)`. Empty string is `false`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_alphanumeric(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - if input.is_empty() { - return 0.0; - } - b(input.chars().all(|c| c.is_alphanumeric())) -} - -/// `validator.isNumeric(str)`. Allows a leading `+` / `-`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_numeric(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_string_owned(input_ptr) else { - return 0.0; - }; - if input.is_empty() { - return 0.0; - } - let to_check = if input.starts_with('-') || input.starts_with('+') { - &input[1..] - } else { - &input[..] - }; - if to_check.is_empty() { - return 0.0; - } - b(to_check.chars().all(|c| c.is_ascii_digit())) -} - -/// `validator.isInt(str)`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_int(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(input.parse::().is_ok()) -} - -/// `validator.isFloat(str)`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_float(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(input.parse::().is_ok()) -} - -/// `validator.isHexadecimal(str)`. Strips an optional `0x`/`0X` -/// prefix before checking. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_hexadecimal(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - if input.is_empty() { - return 0.0; - } - let to_check = input - .strip_prefix("0x") - .or_else(|| input.strip_prefix("0X")) - .unwrap_or(input); - if to_check.is_empty() { - return 0.0; - } - b(to_check.chars().all(|c| c.is_ascii_hexdigit())) -} - -/// `validator.isEmpty(str)`. Returns `true` for null/undefined. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_empty(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 1.0; - }; - b(input.trim().is_empty()) -} - -/// `validator.isJSON(str)`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_json(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(serde_json::from_str::(input).is_ok()) -} - -/// `validator.isLength(str, { min })`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_length_min( - input_ptr: *const StringHeader, - min: f64, -) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(input.len() >= min as usize) -} - -/// `validator.isLength(str, { min, max })`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_length( - input_ptr: *const StringHeader, - min: f64, - max: f64, -) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - let len = input.len(); - b(len >= min as usize && len <= max as usize) -} - -/// `validator.contains(str, seed)`. -/// -/// # Safety -/// -/// Both pointers must be null or Perry-runtime `StringHeader`s. -#[no_mangle] -pub unsafe extern "C" fn js_validator_contains( - input_ptr: *const StringHeader, - seed_ptr: *const StringHeader, -) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - let Some(seed) = read_str(seed_ptr) else { - return 0.0; - }; - b(input.contains(seed)) -} - -/// `validator.equals(str, comparison)`. -/// -/// # Safety -/// -/// Both pointers must be null or Perry-runtime `StringHeader`s. -#[no_mangle] -pub unsafe extern "C" fn js_validator_equals( - input_ptr: *const StringHeader, - comparison_ptr: *const StringHeader, -) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - let Some(comparison) = read_str(comparison_ptr) else { - return 0.0; - }; - b(input == comparison) -} - -/// `validator.isLowercase(str)`. Letters must all be lowercase; -/// non-letter characters are ignored. Empty is `true`. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_lowercase(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(input - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_lowercase())) -} - -/// `validator.isUppercase(str)`. Letters must all be uppercase; -/// non-letter characters are ignored. -/// -/// # Safety -/// -/// `input_ptr` must be null or a Perry-runtime `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_uppercase(input_ptr: *const StringHeader) -> f64 { - let Some(input) = read_str(input_ptr) else { - return 0.0; - }; - b(input - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_uppercase())) -} - -#[cfg(test)] -mod tests { - use super::*; - use perry_ffi::alloc_string; - - fn p(s: &str) -> *const StringHeader { - alloc_string(s).as_raw() as *const _ - } - - #[test] - fn email_validation() { - unsafe { - assert_eq!(js_validator_is_email(p("foo@bar.com")), 1.0); - assert_eq!(js_validator_is_email(p("not-an-email")), 0.0); - assert_eq!(js_validator_is_email(std::ptr::null()), 0.0); - } - } - - #[test] - fn uuid_validation() { - unsafe { - assert_eq!( - js_validator_is_uuid(p("550e8400-e29b-41d4-a716-446655440000")), - 1.0 - ); - assert_eq!(js_validator_is_uuid(p("not-a-uuid")), 0.0); - } - } - - #[test] - fn shared_validation_rules() { - unsafe { - assert_eq!(js_validator_is_email(p("a@bücher.de")), 1.0); - assert_eq!(js_validator_is_email(p("a@prefix[127.0.0.1]")), 1.0); - assert_eq!(js_validator_is_email(p("a@b.com\n")), 0.0); - assert_eq!(js_validator_is_url(p("https://example.com")), 1.0); - assert_eq!(js_validator_is_url(p("not a url")), 0.0); - assert_eq!( - js_validator_is_uuid(p("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")), - 1.0 - ); - assert_eq!( - js_validator_is_uuid(p("550e8400-e29b-41d4-a716-446655440000\n")), - 0.0 - ); - } - } - - #[test] - fn json_validation() { - unsafe { - assert_eq!(js_validator_is_json(p(r#"{"a":1}"#)), 1.0); - assert_eq!(js_validator_is_json(p("[1,2,3]")), 1.0); - assert_eq!(js_validator_is_json(p("not json")), 0.0); - } - } - - #[test] - fn length_bounds() { - unsafe { - assert_eq!(js_validator_is_length(p("hello"), 3.0, 10.0), 1.0); - assert_eq!(js_validator_is_length(p("hi"), 3.0, 10.0), 0.0); - assert_eq!( - js_validator_is_length(p("toolongtoolongtoolong"), 3.0, 10.0), - 0.0 - ); - } - } - - #[test] - fn contains_check() { - unsafe { - assert_eq!(js_validator_contains(p("hello world"), p("world")), 1.0); - assert_eq!(js_validator_contains(p("hello world"), p("xyz")), 0.0); - } - } -} diff --git a/crates/perry-stdlib/Cargo.toml b/crates/perry-stdlib/Cargo.toml index 3dd3510220..0bf7059911 100644 --- a/crates/perry-stdlib/Cargo.toml +++ b/crates/perry-stdlib/Cargo.toml @@ -23,7 +23,7 @@ default = ["full"] # must stay out of this list: release archives enable `full` without linking # their per-program provider archives, and adding an external HTTP pump here # made HTTP-free Linux UI links require libperry_ext_http.a (#5983, #8587). -full = ["http-server", "http-client", "database", "crypto", "compression", "email", "websocket", "image", "scheduler", "ids", "html-parser", "rate-limit", "validation", "net", "tls", "bundled-dotenv", "bundled-lru-cache", "bundled-exponential-backoff", "bundled-events", "bundled-decimal", "bundled-dayjs", "bundled-moment", "bundled-commander", "bundled-streams"] +full = ["http-server", "http-client", "database", "crypto", "compression", "email", "websocket", "image", "scheduler", "ids", "html-parser", "rate-limit", "net", "tls", "bundled-dotenv", "bundled-lru-cache", "bundled-exponential-backoff", "bundled-events", "bundled-decimal", "bundled-dayjs", "bundled-moment", "bundled-commander", "bundled-streams"] # Minimal core - just what's needed for basic programs core = [] @@ -290,10 +290,6 @@ bundled-cron = ["dep:cron", "async-runtime"] rate-limit = ["bundled-ratelimit"] bundled-ratelimit = ["dep:governor", "async-runtime"] -# Validation — `validation` umbrella stays for backwards-compat; -# v0.5.538's well-known flip toggles `bundled-validator` instead. -validation = ["bundled-validator"] -bundled-validator = ["dep:perry-validation"] # UUID/nanoid — `ids` stays as the umbrella for backwards compat; # from v0.5.534 onwards the per-binding split (`bundled-uuid` / @@ -442,7 +438,6 @@ cron = { version = "0.17", optional = true } governor = { version = "0.10", optional = true } # Validation -perry-validation = { workspace = true, optional = true } # IDs uuid = { version = "1.23", features = ["v4", "v1", "v3", "v5", "v7"], optional = true } diff --git a/crates/perry-stdlib/src/lib.rs b/crates/perry-stdlib/src/lib.rs index 1a2970af77..17b47f6bbd 100644 --- a/crates/perry-stdlib/src/lib.rs +++ b/crates/perry-stdlib/src/lib.rs @@ -407,17 +407,6 @@ pub mod ratelimit; #[cfg(feature = "bundled-ratelimit")] pub use ratelimit::*; -// === Validation === -// `validation` umbrella now expands to `bundled-validator` -// (v0.5.538). Per-binding gate lets the well-known flip swap the -// validator wrapper out without affecting the rest of the -// validation surface (none — there's just the one wrapper today, -// but the split unblocks future additions). -#[cfg(feature = "bundled-validator")] -pub mod validator; -#[cfg(feature = "bundled-validator")] -pub use validator::*; - // === IDs === // `bundled-uuid` / `bundled-nanoid` (v0.5.534) replace the old // `ids` umbrella so the well-known flip (#466 Phase 4) can toggle diff --git a/crates/perry-stdlib/src/validator.rs b/crates/perry-stdlib/src/validator.rs deleted file mode 100644 index 490a5c56d6..0000000000 --- a/crates/perry-stdlib/src/validator.rs +++ /dev/null @@ -1,425 +0,0 @@ -//! Validator module (validator compatible) -//! -//! Native implementation of the 'validator' npm package. -//! Provides string validation functions. - -use perry_runtime::StringHeader; - -use crate::common::string_from_header; - -// These synchronous predicates perform no Perry allocation or callbacks, so -// the original string can remain borrowed for the complete operation. -unsafe fn validate_borrowed(input: *const StringHeader, check: impl FnOnce(&str) -> bool) -> f64 { - if crate::common::map_string_header_bytes(input, |bytes| { - std::str::from_utf8(bytes).is_ok_and(check) - }) - .unwrap_or(false) - { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is a valid email address -/// validator.isEmail(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_email(input_ptr: *const StringHeader) -> f64 { - validate_borrowed(input_ptr, perry_validation::is_email) -} - -/// Check if a string is a valid URL -/// validator.isURL(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_url(input_ptr: *const StringHeader) -> f64 { - validate_borrowed(input_ptr, perry_validation::is_url) -} - -/// Check if a string is a valid UUID -/// validator.isUUID(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_uuid(input_ptr: *const StringHeader) -> f64 { - validate_borrowed(input_ptr, perry_validation::is_uuid) -} - -/// Check if a string contains only alphabetic characters -/// validator.isAlpha(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_alpha(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input.is_empty() { - return 0.0; - } - - if input.chars().all(|c| c.is_alphabetic()) { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string contains only alphanumeric characters -/// validator.isAlphanumeric(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_alphanumeric(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input.is_empty() { - return 0.0; - } - - if input.chars().all(|c| c.is_alphanumeric()) { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string contains only numeric characters -/// validator.isNumeric(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_numeric(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input.is_empty() { - return 0.0; - } - - // Allow optional leading minus sign - let to_check = if input.starts_with('-') || input.starts_with('+') { - &input[1..] - } else { - &input[..] - }; - - if to_check.is_empty() { - return 0.0; - } - - if to_check.chars().all(|c| c.is_ascii_digit()) { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is a valid integer -/// validator.isInt(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_int(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input.parse::().is_ok() { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is a valid float -/// validator.isFloat(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_float(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input.parse::().is_ok() { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is a valid hexadecimal -/// validator.isHexadecimal(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_hexadecimal(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input.is_empty() { - return 0.0; - } - - // Remove optional 0x prefix - let to_check = input - .strip_prefix("0x") - .or_else(|| input.strip_prefix("0X")) - .unwrap_or(&input); - - if to_check.is_empty() { - return 0.0; - } - - if to_check.chars().all(|c| c.is_ascii_hexdigit()) { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is empty (after trimming whitespace) -/// validator.isEmpty(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_empty(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 1.0, // null/undefined is considered empty - }; - - if input.trim().is_empty() { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is valid JSON -/// validator.isJSON(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_json(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if serde_json::from_str::(&input).is_ok() { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string has a minimum length -/// validator.isLength(str, { min }) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_length_min( - input_ptr: *const StringHeader, - min: f64, -) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input.len() >= min as usize { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is within a length range -/// validator.isLength(str, { min, max }) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_length( - input_ptr: *const StringHeader, - min: f64, - max: f64, -) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - let len = input.len(); - if len >= min as usize && len <= max as usize { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string contains a substring -/// validator.contains(str, seed) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_contains( - input_ptr: *const StringHeader, - seed_ptr: *const StringHeader, -) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - let seed = match string_from_header(seed_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input.contains(&seed) { - 1.0 - } else { - 0.0 - } -} - -/// Check if strings are equal -/// validator.equals(str, comparison) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_equals( - input_ptr: *const StringHeader, - comparison_ptr: *const StringHeader, -) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - let comparison = match string_from_header(comparison_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input == comparison { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is lowercase -/// validator.isLowercase(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_lowercase(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_lowercase()) - { - 1.0 - } else { - 0.0 - } -} - -/// Check if a string is uppercase -/// validator.isUppercase(str) -> boolean -#[no_mangle] -pub unsafe extern "C" fn js_validator_is_uppercase(input_ptr: *const StringHeader) -> f64 { - let input = match string_from_header(input_ptr) { - Some(s) => s, - None => return 0.0, - }; - - if input - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_uppercase()) - { - 1.0 - } else { - 0.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - use perry_runtime::gc::RuntimeHandleScope; - - #[test] - fn validator_borrows_original_heap_payload_and_preserves_bad_input_results() { - let scope = RuntimeHandleScope::new(); - let bytes = b"550e8400-e29b-41d4-a716-446655440000"; - let input = scope.root_string_ptr(perry_runtime::string::js_string_from_bytes( - bytes.as_ptr(), - bytes.len() as u32, - )); - let ptr = input.get_raw_const_ptr::(); - let mut scratch = [0; perry_runtime::value::SHORT_STRING_MAX_LEN]; - let value = f64::from_bits( - perry_runtime::value::JSValue::string_ptr(ptr as *mut StringHeader).bits(), - ); - let (original, _) = perry_runtime::string::str_bytes_from_jsvalue(value, &mut scratch) - .expect("a heap string has a payload"); - // The canonical reader answers a heap string with its payload in place; - // only a short immediate string is decoded into `scratch`. - assert_ne!(original, scratch.as_ptr()); - assert_eq!( - unsafe { - validate_borrowed(ptr, |s| { - assert_eq!( - s.as_ptr(), - original, - "validation must not copy the heap subject" - ); - s.as_bytes() == bytes - }) - }, - 1.0 - ); - assert_eq!(unsafe { js_validator_is_uuid(ptr) }, 1.0); - let invalid = scope.root_string_ptr(perry_runtime::string::js_string_from_bytes( - b"a\x80b".as_ptr(), - 3, - )); - for check in [ - js_validator_is_email, - js_validator_is_url, - js_validator_is_uuid, - ] { - for ptr in [ - std::ptr::null(), - 1usize as *const StringHeader, - 0x40000usize as *const StringHeader, - invalid.get_raw_const_ptr(), - ] { - assert_eq!(unsafe { check(ptr) }, 0.0); - } - } - } - - #[test] - fn validator_bindings_use_shared_email_url_and_uuid_rules() { - let scope = RuntimeHandleScope::new(); - for (text, expected) in [ - ("a@bücher.de", [1.0, 0.0, 0.0]), - ("a@prefix[127.0.0.1]", [1.0, 0.0, 0.0]), - ("a@b.com\n", [0.0, 0.0, 0.0]), - ("https://example.com", [0.0, 1.0, 0.0]), - ("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", [0.0, 0.0, 1.0]), - ] { - let input = scope.root_string_ptr(perry_runtime::string::js_string_from_bytes( - text.as_ptr(), - text.len() as u32, - )); - for (check, expected) in [ - js_validator_is_email, - js_validator_is_url, - js_validator_is_uuid, - ] - .into_iter() - .zip(expected) - { - assert_eq!( - unsafe { check(input.get_raw_const_ptr()) }, - expected, - "{text:?}" - ); - } - } - } -} diff --git a/crates/perry-ui-android/src/stdlib_stubs.rs b/crates/perry-ui-android/src/stdlib_stubs.rs index 4351918c1f..a086f0c45c 100644 --- a/crates/perry-ui-android/src/stdlib_stubs.rs +++ b/crates/perry-ui-android/src/stdlib_stubs.rs @@ -1470,70 +1470,6 @@ pub extern "C" fn js_uuid_validate() -> i64 { pub extern "C" fn js_uuid_version() -> i64 { 0 } -#[no_mangle] -pub extern "C" fn js_validator_contains() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_equals() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_alpha() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_alphanumeric() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_email() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_empty() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_float() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_hexadecimal() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_int() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_json() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_length() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_lowercase() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_numeric() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_uppercase() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_url() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_validator_is_uuid() -> i64 { - 0 -} // readline (#347) — TUI use case isn't relevant on Android, so stubs // return inert values (handle 0, no-op for everything). The `_active` // stub returns 0 so the host event loop doesn't keep ticking. diff --git a/crates/perry-validation/Cargo.toml b/crates/perry-validation/Cargo.toml deleted file mode 100644 index f2bf1d36c5..0000000000 --- a/crates/perry-validation/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "perry-validation" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Shared borrowed string validators for Perry's bundled and extension bindings" - -[lints] -workspace = true - -[dependencies] -idna = "1" -url.workspace = true - -[dev-dependencies] -# The previous implementations are correctness references, never production dependencies. -validator = "=0.21.0" -regex.workspace = true diff --git a/crates/perry-validation/UPSTREAM_VALIDATOR_LICENSE b/crates/perry-validation/UPSTREAM_VALIDATOR_LICENSE deleted file mode 100644 index 1a4c4809f7..0000000000 --- a/crates/perry-validation/UPSTREAM_VALIDATOR_LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Vincent Prouillet - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/crates/perry-validation/src/lib.rs b/crates/perry-validation/src/lib.rs deleted file mode 100644 index c014b03f8c..0000000000 --- a/crates/perry-validation/src/lib.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Fixed-grammar validators shared by Perry's two validator bindings. -//! -//! These functions borrow their inputs and call no Perry allocator or callback. -//! UUID and the ASCII email fast path allocate nothing. IDNA conversion and URL -//! parsing retain their existing library behavior and temporary native storage. -//! No regular-expression compiler, program cache or matcher is involved. -//! -//! Email behavior follows the previously used `validator` 0.21.0 implementation -//! (https://github.com/Keats/validator), including its IP-literal suffix rule. -//! Its license is retained in `UPSTREAM_VALIDATOR_LICENSE`. - -/// Check the existing 8-4-4-4-12 ASCII hexadecimal UUID grammar. -/// Version and variant bits are deliberately unrestricted, as before. -pub fn is_uuid(input: &str) -> bool { - let bytes = input.as_bytes(); - bytes.len() == 36 - && bytes.iter().enumerate().all(|(i, b)| { - if matches!(i, 8 | 13 | 18 | 23) { - *b == b'-' - } else { - b.is_ascii_hexdigit() - } - }) -} - -/// Check the email grammar and length limits previously supplied by validator. -pub fn is_email(input: &str) -> bool { - // At most 64 ASCII local bytes, '@', and 255 four-byte domain characters. - // Reject longer input before scanning it or invoking IDNA. - if input.len() > 64 + 1 + 255 * 4 { - return false; - } - let Some((local, domain)) = input.rsplit_once('@') else { - return false; - }; - if local.is_empty() - || local.len() > 64 - || !local - .bytes() - .all(|b| b.is_ascii_alphanumeric() || b".!#$%&'*+/=?^_`{|}~-".contains(&b)) - || domain.chars().count() > 255 - { - return false; - } - if domain_part(domain) { - return true; - } - idna::domain_to_ascii(domain).is_ok_and(|ascii| domain_part(&ascii)) -} - -fn domain_part(domain: &str) -> bool { - if domain.split('.').all(|label| { - let b = label.as_bytes(); - !b.is_empty() - && b.len() <= 63 - && b[0].is_ascii_alphanumeric() - && b[b.len() - 1].is_ascii_alphanumeric() - && b.iter().all(|b| b.is_ascii_alphanumeric() || *b == b'-') - }) { - return true; - } - // The prior literal regex was anchored only at the end. Preserve that - // observable suffix behavior, including prefixes before '[', in this - // engine-removal change. IpAddr enforces the same IPv4/IPv6 grammar. - domain - .strip_suffix(']') - .and_then(|s| s.rsplit_once('[')) - .is_some_and(|(_, ip)| ip.parse::().is_ok()) -} - -/// Preserve the URL parser used by the previous validator trait. -pub fn is_url(input: &str) -> bool { - url::Url::parse(input).is_ok() -} - -#[cfg(test)] -mod tests; diff --git a/crates/perry-validation/src/tests.rs b/crates/perry-validation/src/tests.rs deleted file mode 100644 index 8ad34094eb..0000000000 --- a/crates/perry-validation/src/tests.rs +++ /dev/null @@ -1,189 +0,0 @@ -use super::*; -use validator::{ValidateEmail, ValidateUrl}; - -#[test] -fn uuid_matches_previous_grammar_under_edits() { - let reference = regex::Regex::new( - r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", - ) - .unwrap(); - let original = "550e8400-e29b-41d4-a716-446655440000"; - let mut checked = 0; - let mut check = |s: &str| { - assert_eq!(is_uuid(s), reference.is_match(s), "{s:?}"); - checked += 1; - }; - for at in 0..original.len() { - for c in 0..=255u8 { - let mut s = original.to_owned(); - s.replace_range(at..at + 1, &char::from(c).to_string()); - check(&s); - } - let mut s = original.to_owned(); - s.remove(at); - check(&s); - } - for at in 0..=original.len() { - for c in ['0', '-', '\0', '\n', 'é', '𝟘'] { - let mut s = original.to_owned(); - s.insert(at, c); - check(&s); - } - } - for s in [ - "00000000-0000-0000-0000-000000000000", - "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", - "550E8400-e29B-F1d4-0716-446655440000", - "", - ] { - check(s); - } - assert_eq!(checked, 9478); -} - -#[test] -fn email_matches_previous_grammar_for_short_structures() { - // Exercise every placement of the grammar's punctuation, including the - // previous unanchored IP-literal search, against the actual old library. - let alphabet = ['a', '0', '-', '.', '[', ']', ':', '@', '_', '!']; - let mut checked = 0; - for len in 0..=4 { - for mut code in 0..alphabet.len().pow(len) { - let mut s = String::new(); - for _ in 0..len { - s.push(alphabet[code % alphabet.len()]); - code /= alphabet.len(); - } - for input in [s.clone(), format!("{s}@a"), format!("a@{s}")] { - assert_eq!(is_email(&input), input.validate_email(), "{input:?}"); - checked += 1; - } - } - } - assert_eq!(checked, 33333); -} - -#[test] -fn email_preserves_unicode_lengths_ip_literals_and_idna() { - let local_parts = [ - "a".to_owned(), - "a".repeat(63), - "a".repeat(64), - "a".repeat(65), - "!#$%&'*+/=?^_`{|}~.-".to_owned(), - "é".repeat(32), - "a\n".to_owned(), - "".to_owned(), - ]; - let mut domains: Vec = [ - "localhost", - "a.b", - "a..b", - ".a", - "a.", - "-a", - "a-", - "a_b", - "127.0.0.1", - "[127.0.0.1]", - "[127.0.0.256]", - "[01.2.3.4]", - "[2001:dB8::1]", - "[::ffff:127.0.0.1]", - "[2001:db8::12345]", - "[::1%eth0]", - "prefix[127.0.0.1]", - "[[::1]", - "[::1]suffix", - "[::1]\n", - "[::1]\r\n", - "a\0b", - "a\nb", - "exam_ple.com", - "例え.テスト", - "उदाहरण.परीक्षा", - "bücher.de", - "xn--bcher-kva.de", - "K.com", - "A.com", - "。", - "a。b", - "a。", - "a\u{200d}b.com", - "a\u{200c}b.com", - "a\u{00ad}b.com", - "a\u{0301}.com", - "😀.com", - "é[::1]", - "é", - "", - "[::]", - ] - .into_iter() - .map(str::to_owned) - .collect(); - for n in [1, 62, 63, 64, 254, 255, 256] { - domains.push("a".repeat(n)); - domains.push(format!("{}.com", "a".repeat(n))); - domains.push("é".repeat(n)); - domains.push(format!("{}[::1]", "é".repeat(n))); - } - for n in [252, 253, 254, 255, 256] { - let mut s = "a.".repeat(n / 2); - if n % 2 != 0 { - s.push('a'); - } - domains.push(s); - } - for local in &local_parts { - for domain in &domains { - let s = format!("{local}@{domain}"); - assert_eq!(is_email(&s), s.validate_email(), "{s:?}"); - } - } - assert!( - is_email("a@prefix[127.0.0.1]"), - "retain the prior suffix behavior" - ); - assert!(!is_email("a@[127.0.0.1]\n")); -} - -#[test] -fn email_preserves_each_byte_in_local_and_domain_positions() { - for b in 0..=255u8 { - let c = char::from(b); - for s in [ - format!("{c}@example.com"), - format!("a{c}b@example.com"), - format!("a@{c}b.com"), - format!("a@a{c}b.com"), - format!("a@ab{c}.com"), - format!("a@{c}[::1]"), - format!("a@[127.0.0.{c}]"), - format!("a@[::{c}]"), - ] { - assert_eq!(is_email(&s), s.validate_email(), "{s:?}"); - } - } -} - -#[test] -fn url_uses_the_same_parser_as_the_previous_trait() { - for s in [ - "https://example.com", - "http://localhost:80", - "ftp://host/", - "mailto:a@b", - "file:///a", - "data:,x", - "https://例え.テスト/a", - "http", - "//example.com", - "", - "https://[::1]/", - "https://[invalid]/", - "https://x\n.y", - ] { - assert_eq!(is_url(s), s.validate_url(), "{s:?}"); - } -} diff --git a/crates/perry/src/commands/stdlib_features.rs b/crates/perry/src/commands/stdlib_features.rs index e45b6d39b7..b0117047f2 100644 --- a/crates/perry/src/commands/stdlib_features.rs +++ b/crates/perry/src/commands/stdlib_features.rs @@ -151,11 +151,6 @@ pub fn module_to_features(module: &str) -> &'static [&'static str] { // well-known flip can route to perry-ext-cron. "cron" | "node-cron" => &["bundled-cron"], - // ── Validation (validator.js) ───────────────────────────────── - // `validation` umbrella retained for backwards-compat; - // per-binding gate is `bundled-validator` (v0.5.538). - "validator" => &["bundled-validator"], - // ── argon2 ──────────────────────────────────────────────────── // argon2 split off into `bundled-argon2` (v0.5.537) — same // reason as bcrypt above. Note: NATIVE_MODULES doesn't list diff --git a/crates/perry/well_known_bindings.toml b/crates/perry/well_known_bindings.toml index 70b7e3b59c..c0c6b2f983 100644 --- a/crates/perry/well_known_bindings.toml +++ b/crates/perry/well_known_bindings.toml @@ -131,18 +131,6 @@ repo = "https://github.com/ranisalt/node-argon2" ref = "786de7152f95881b0683aea1d2ca60ed0d6d9e2f" ported-at = "0.45.1" date = "2026-07-30" -[bindings.validator] -crate = "perry-ext-validator" -lib = "perry_ext_validator" -tracking = "#466" - -[bindings.validator.upstream] -version = "13.15.35" -sha256 = "f9a6b506bd9eda8df9d2a4120613426948d9f66cde1b6d5fad3406758d2f81f4" -repo = "https://github.com/validatorjs/validator.js" -ref = "7a8079709cd4cb27b2a1846e6f6508d68c9d928f" -ported-at = "13.15.35" -date = "2026-07-30" [bindings.lru-cache] crate = "perry-ext-lru-cache" lib = "perry_ext_lru_cache" diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index 81c488c26b..1dfe4903dd 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -1,6 +1,6 @@ // Auto-generated from Perry's API manifest (#465). Do not edit by hand. // Source: perry-api-manifest::API_MANIFEST -// Coverage: 2090 entries across 135 modules +// Coverage: 2085 entries across 134 modules type PerryI8 = number & { readonly __perryI8?: never }; type PerryI16 = number & { readonly __perryI16?: never }; @@ -4428,19 +4428,6 @@ declare module "v8" { export function writeHeapSnapshot(...args: any[]): any; } -declare module "validator" { - /** stdlib */ - export function isEmail(s: string): boolean; - /** stdlib */ - export function isEmpty(s: string): boolean; - /** stdlib */ - export function isJSON(s: string): boolean; - /** stdlib */ - export function isURL(s: string): boolean; - /** stdlib */ - export function isUUID(s: string): boolean; -} - declare module "vm" { /** stdlib */ export class Script { [key: string]: any; } diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index affdd14dcb..128b24f1e8 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -2,7 +2,7 @@ This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target. -Total: 3032 entries across 137 modules. +Total: 3027 entries across 136 modules. ## Modules @@ -137,7 +137,6 @@ Total: 3032 entries across 137 modules. - [`util/types`](#utiltypes) - [`uuid`](#uuid) - [`v8`](#v8) -- [`validator`](#validator) - [`vm`](#vm) - [`wasi`](#wasi) - [`worker_threads`](#worker_threads) @@ -3987,16 +3986,6 @@ Total: 3032 entries across 137 modules. - `promiseHooks` - `startupSnapshot` -## `validator` - -### Methods - -- `isEmail` — module -- `isEmpty` — module -- `isJSON` — module -- `isURL` — module -- `isUUID` — module - ## `vm` ### Classes diff --git a/docs/src/native-libraries/governance.md b/docs/src/native-libraries/governance.md index 9813d11331..4886a87a77 100644 --- a/docs/src/native-libraries/governance.md +++ b/docs/src/native-libraries/governance.md @@ -120,7 +120,6 @@ from `well_known_bindings.toml`. Regenerate this table with | `perry-ext-typescript` | `typescript` | Source package | Compile the upstream package source | Bundled; migration pending | | `perry-ext-undici` | `undici` | Source package | Compile the upstream package source | Bundled; migration pending | | `perry-ext-uuid` | `uuid` | Source package | Compile the upstream package source | Bundled; migration pending | -| `perry-ext-validator` | `validator` | Source package | Compile the upstream package source | Bundled; migration pending | | `perry-ext-ws` | `ws` | Runtime API | Keep near core; consolidate when practical | Bundled; retained | | `perry-ext-zlib` | `zlib` | Runtime API | Keep near core; consolidate when practical | Bundled; retained | diff --git a/tests/release/packages/next-app-route/provider/stdlib/Cargo.toml b/tests/release/packages/next-app-route/provider/stdlib/Cargo.toml index d2f0904fef..1523a800c4 100644 --- a/tests/release/packages/next-app-route/provider/stdlib/Cargo.toml +++ b/tests/release/packages/next-app-route/provider/stdlib/Cargo.toml @@ -18,7 +18,6 @@ perry-stdlib-core = { package = "perry-stdlib", path = "../../../../../../crates "ids", "html-parser", "rate-limit", - "validation", "bundled-dotenv", "bundled-lru-cache", "bundled-exponential-backoff", diff --git a/workspace-architecture.json b/workspace-architecture.json index 8f9365ffc4..3f5c8d42fd 100644 --- a/workspace-architecture.json +++ b/workspace-architecture.json @@ -25,7 +25,7 @@ ] }, "baseline": { - "workspace_members": 82, + "workspace_members": 80, "default_dependency_closure": [ "perry", "perry-api-manifest", @@ -68,8 +68,8 @@ "perry-updater" ], "decision_counts": { - "externalize": 32, - "keep": 45, + "externalize": 31, + "keep": 44, "merge": 1, "remove": 1, "review": 3 @@ -320,11 +320,6 @@ "decision": "externalize", "migration": "compile-source" }, - "perry-ext-validator": { - "category": "binding", - "decision": "externalize", - "migration": "compile-source" - }, "perry-ext-ws": { "category": "binding", "decision": "keep", @@ -435,10 +430,6 @@ "category": "runtime-core", "decision": "keep" }, - "perry-validation": { - "category": "runtime-core", - "decision": "keep" - }, "perry-wasm-host": { "category": "runtime-core", "decision": "keep"