diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4217698..a64ba53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: include: - build: pinned os: ubuntu-20.04 - rust: 1.69 + rust: 1.71 # Fails on pinned version because the output changed, # so we're excluding it, but it's still tested on stable and nightly. EXCLUDE_UI_TESTS: "pattern_mismatched_types,newtype" @@ -88,7 +88,7 @@ jobs: shared-key: "rust-${{ matrix.build }}-test" - name: Build xtask - run: cargo build --manifest-path ./xtask/Cargo.toml + run: cargo build --verbose --manifest-path ./xtask/Cargo.toml - name: Build docs run: cargo doc --all-features diff --git a/.gitignore b/.gitignore index b7e4dc0..bca94e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target -/Cargo.lock +/**/Cargo.lock *.snap.new diff --git a/README.md b/README.md index 1960d97..1fa3f09 100644 --- a/README.md +++ b/README.md @@ -74,35 +74,37 @@ if let Err(e) = data.validate(&()) { ### Available validation rules -| name | format | validation | feature flag | -| ------------ | ------------------------------------------------ | ---------------------------------------------------- | -------------- | -| required | `#[garde(required)]` | is value set | - | -| ascii | `#[garde(ascii)]` | only contains ASCII | - | -| alphanumeric | `#[garde(alphanumeric)]` | only letters and digits | - | -| email | `#[garde(email)]` | an email according to the HTML5 spec[^1] | `email` | -| url | `#[garde(url)]` | a URL | `url` | -| ip | `#[garde(ip)]` | an IP address (either IPv4 or IPv6) | - | -| ipv4 | `#[garde(ipv4)]` | an IPv4 address | - | -| ipv6 | `#[garde(ipv6)]` | an IPv6 address | - | -| credit card | `#[garde(credit_card)]` | a credit card number | `credit-card` | -| phone number | `#[garde(phone_number)]` | a phone number | `phone-number` | -| length | `#[garde(length(min=, max=)]` | a container with length in `min..=max` | - | -| byte_length | `#[garde(byte_length(min=, max=)]` | a byte sequence with length in `min..=max` | - | -| range | `#[garde(range(min=, max=))]` | a number in the range `min..=max` | - | -| contains | `#[garde(contains())]` | a string-like value containing a substring | - | -| prefix | `#[garde(prefix())]` | a string-like value prefixed by some string | - | -| suffix | `#[garde(suffix())]` | a string-like value suffixed by some string | - | -| pattern | `#[garde(pattern(""))]` | a string-like value matching some regular expression | `regex` | -| pattern | `#[garde(pattern())]` | a string-like value matched by some [Matcher](https://docs.rs/garde/latest/garde/rules/pattern/trait.Matcher.html) | - | -| dive | `#[garde(dive)]` | nested validation, calls `validate` on the value | - | -| skip | `#[garde(skip)]` | skip validation | - | -| custom | `#[garde(custom())]` | a custom validator | - | +| name | format | validation | feature flag | +| -------------- | --------------------------------------------------- | ---------------------------------------------------- | -------------- | +| required | `#[garde(required)]` | is value set | - | +| ascii | `#[garde(ascii)]` | only contains ASCII | - | +| alphanumeric | `#[garde(alphanumeric)]` | only letters and digits | - | +| email | `#[garde(email)]` | an email according to the HTML5 spec[^1] | `email` | +| url | `#[garde(url)]` | a URL | `url` | +| ip | `#[garde(ip)]` | an IP address (either IPv4 or IPv6) | - | +| ipv4 | `#[garde(ipv4)]` | an IPv4 address | - | +| ipv6 | `#[garde(ipv6)]` | an IPv6 address | - | +| credit card | `#[garde(credit_card)]` | a credit card number | `credit-card` | +| phone number | `#[garde(phone_number)]` | a phone number | `phone-number` | +| length | `#[garde(length(min=, max=)]` | a container with length in `min..=max` | - | +| char_count | `#[garde(char_count(min=, max=)]` | a string with character count in `min..=max` | - | +| grapheme_count | `#[garde(grapheme_count(min=, max=)]` | a string with grapheme count in `min..=max` | `unicode` | +| range | `#[garde(range(min=, max=))]` | a number in the range `min..=max` | - | +| contains | `#[garde(contains())]` | a string-like value containing a substring | - | +| prefix | `#[garde(prefix())]` | a string-like value prefixed by some string | - | +| suffix | `#[garde(suffix())]` | a string-like value suffixed by some string | - | +| pattern | `#[garde(pattern(""))]` | a string-like value matching some regular expression | `regex` | +| pattern | `#[garde(pattern())]` | a string-like value matched by some [Matcher](https://docs.rs/garde/latest/garde/rules/pattern/trait.Matcher.html) | - | +| dive | `#[garde(dive)]` | nested validation, calls `validate` on the value | - | +| skip | `#[garde(skip)]` | skip validation | - | +| custom | `#[garde(custom())]` | a custom validator | - | Additional notes: - `required` is only available for `Option` fields. - For `length` and `range`, either `min` or `max` may be omitted, but not both. - `length` and `range` use an *inclusive* upper bound (`min..=max`). -- `length` uses `.chars().count()` for UTF-8 strings instead of `.len()`. +- `length` uses `.len()` for UTF-8 strings and calculate a size in *bytes*. +- Most likely, you want to use `grapheme_count` instead of `char_count`. For more information go to its [documentation](https://docs.rs/garde/latest/garde/rules/grapheme_count/index.html). - For `contains`, `prefix`, and `suffix`, the pattern must be a string literal, because the `Pattern` API [is currently unstable](https://github.com/rust-lang/rust/issues/27721). - Garde does not enable the default features of the `regex` crate - if you need extra regex features (e.g. Unicode) or better performance, add a dependency on `regex = "1"` to your `Cargo.toml`. @@ -157,7 +159,10 @@ with `#[garde(transparent)]`: ```rust #[derive(garde::Validate)] #[garde(transparent)] -struct Username(#[garde(length(min = 3, max = 20))] String); +struct Username( + #[garde(length(max = 50), grapheme_count(min = 3, max = 20))] + String, +); #[derive(garde::Validate)] struct User { @@ -171,20 +176,20 @@ The `username` field in the above example will inherit all the validation rules ```rust,ignore User { - username: Username("") + username: Username("".into()) }.validate(&()) -"username: length is lower than 3" +"username: grapheme count is lower than 3" ``` Without the `#[garde(transparent)]` attribute, it would instead be: ```rust,ignore User { - username: Username("") + username: Username("".into()) }.validate(&()) -"username[0]: length is lower than 3" +"username[0]: grapheme count is lower than 3" ``` Structs with the `#[garde(transparent)]` attribute may have more than one field, but there must be only one unskipped field. That means every field other than the one you wish to validate must be `#[garde(skip)]`. @@ -337,14 +342,14 @@ struct Bar { | name | description | extra dependencies | |--------------------------|-----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------| -| `derive` | Enables the usage of the `derive(Validate)` macro | [`garde_derive`](https://crates.io/crates/garde_derive) | +| `derive` | Enables the usage of the `derive(Validate)` macro. | [`garde_derive`](https://crates.io/crates/garde_derive) | | `url` | Validation of URLs via the `url` crate. | [`url`](https://crates.io/crates/url) | -| `email` | Validation of emails according to [HTML5](https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address) | [`regex`](https://crates.io/crates/regex), [`once_cell`](https://crates.io/crates/once_cell) | -| `email-idna` | Support for [Internationalizing Domain Names for Applications](https://url.spec.whatwg.org/#idna) in email addresses | [`idna`](https://crates.io/crates/idna) | -| `regex` | Support for regular expressions in `pattern` via the `regex` crate | [`regex`](https://crates.io/crates/regex), [`once_cell`](https://crates.io/crates/once_cell) | -| `credit-card` | Validation of credit card numbers via the `card-validate` crate | [`card-validate`](https://crates.io/crates/card-validate) | -| `phone-number` | Validation of phone numbers via the `phonenumber` crate | [`phonenumber`](https://crates.io/crates/phonenumber) | - +| `email` | Validation of emails according to [HTML5](https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address). | [`regex`](https://crates.io/crates/regex), [`once_cell`](https://crates.io/crates/once_cell) | +| `email-idna` | Support for [Internationalizing Domain Names for Applications](https://url.spec.whatwg.org/#idna) in email addresses. | [`idna`](https://crates.io/crates/idna) | +| `regex` | Support for regular expressions in `pattern` via the `regex` crate. | [`regex`](https://crates.io/crates/regex), [`once_cell`](https://crates.io/crates/once_cell) | +| `credit-card` | Validation of credit card numbers via the `card-validate` crate. | [`card-validate`](https://crates.io/crates/card-validate) | +| `phone-number` | Validation of phone numbers via the `phonenumber` crate. | [`phonenumber`](https://crates.io/crates/phonenumber) | +| `unicode` | Validation of grapheme count in strings via the `unicode-segmentation` crate. | [`unicode-segmentation`](https://crates.io/crates/unicode-segmentation) | ### Why `garde`? diff --git a/garde/Cargo.toml b/garde/Cargo.toml index 36dcda2..979a02f 100644 --- a/garde/Cargo.toml +++ b/garde/Cargo.toml @@ -20,6 +20,7 @@ default = [ "email", "email-idna", "regex", + "unicode", ] serde = ["dep:serde", "compact_str/serde"] derive = ["dep:garde_derive"] @@ -31,12 +32,13 @@ 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"] +unicode = ["dep:unicode-segmentation"] [dependencies] garde_derive = { version = "0.17.0", path = "../garde_derive", optional = true, default-features = false } -smallvec = { version = "1.11.0", default-features = false } -compact_str = { version = "0.7.1", default-features = false } +smallvec = { version = "1.11", default-features = false } +compact_str = { version = "0.7", default-features = false } serde = { version = "1", features = ["derive"], optional = true } url = { version = "2", optional = true } @@ -46,7 +48,8 @@ regex = { version = "1", default-features = false, features = [ "std", ], optional = true } once_cell = { version = "1", optional = true } -idna = { version = "0.3", optional = true } +idna = { version = "0.5", optional = true } +unicode-segmentation = { version = "1.10", optional = true } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] js-sys = { version = "0.3", optional = true } @@ -54,14 +57,14 @@ 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" +owo-colors = { version = "4.0" } +glob = "0.3" [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] -wasm-bindgen-test = "0.3.38" +wasm-bindgen-test = "0.3" [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "validation" diff --git a/garde/src/rules/byte_length.rs b/garde/src/rules/byte_length.rs index 8cf17c4..4c4bf70 100644 --- a/garde/src/rules/byte_length.rs +++ b/garde/src/rules/byte_length.rs @@ -14,7 +14,6 @@ //! [`ByteLength`] is implemented for any `T: HasByteLength`. //! //! In case of string types, [`HasByteLength::byte_length`] should return the number of _bytes_ as opposed to the number of _characters_. -//! For validation of length counted in _characters_, see the [`crate::rules::length`] rule. //! //! Here's what implementing the trait for a custom string-like type might look like: //! ```rust @@ -31,6 +30,7 @@ use super::AsStr; use crate::error::Error; +#[deprecated = "the `byte_length` attribute is deprecated. Use `length` instead. (See https://github.com/jprochazk/garde/issues/84)"] pub fn apply(v: &T, (min, max): (usize, usize)) -> Result<(), Error> { if let Err(e) = v.validate_byte_length(min, max) { match e { diff --git a/garde/src/rules/char_count.rs b/garde/src/rules/char_count.rs new file mode 100644 index 0000000..c2df138 --- /dev/null +++ b/garde/src/rules/char_count.rs @@ -0,0 +1,122 @@ +//! Character count validation. Works as `string.chars().count()` and counts **USVs** (Unicode Scalar Value). +//! +//! It's important to remember that `char` represents a **USV**, and [may not match your idea](https://stackoverflow.com/a/46290728) of +//! what a 'character' is. Using grapheme clusters (the [`crate::rules::grapheme_count`] rule) may be what you actually want. +//! +//! ```rust +//! #[derive(garde::Validate)] +//! struct Test { +//! #[garde(char_count(min=1, max=100))] +//! v: String, +//! } +//! ``` +//! +//! The entrypoint is the [`CharCount`] trait. Implementing this trait for a type allows that type to be used with the `#[garde(char_count(...))]` rule. +//! +//! For validation of length counted in _bytes_, see the [`crate::rules::length`] rule. +//! +//! Here's what implementing the trait for a custom string-like type might look like: +//! ```rust +//! #[repr(transparent)] +//! struct MyString(String); +//! +//! impl garde::rules::char_count::HasCharCount for MyString { +//! fn char_count(&self) -> usize { +//! self.0.chars().count() +//! } +//! } +//! ``` + +use crate::error::Error; + +pub fn apply(v: &T, (min, max): (usize, usize)) -> Result<(), Error> { + if let Err(e) = v.validate_char_count(min, max) { + match e { + InvalidLength::Min => { + return Err(Error::new(format!("character count is lower than {min}"))) + } + InvalidLength::Max => { + return Err(Error::new(format!("character count is greater than {max}"))) + } + } + } + Ok(()) +} + +pub trait CharCount { + fn validate_char_count(&self, min: usize, max: usize) -> Result<(), InvalidLength>; +} + +pub enum InvalidLength { + Min, + Max, +} + +#[allow(clippy::len_without_is_empty)] +pub trait HasCharCount { + fn char_count(&self) -> usize; +} + +impl CharCount for T { + fn validate_char_count(&self, min: usize, max: usize) -> Result<(), InvalidLength> { + let len = HasCharCount::char_count(self); + if len < min { + Err(InvalidLength::Min) + } else if len > max { + Err(InvalidLength::Max) + } else { + Ok(()) + } + } +} + +impl CharCount for Option { + fn validate_char_count(&self, min: usize, max: usize) -> Result<(), InvalidLength> { + match self { + Some(value) => value.validate_char_count(min, max), + None => Ok(()), + } + } +} + +impl HasCharCount for String { + fn char_count(&self) -> usize { + self.chars().count() + } +} + +impl<'a> HasCharCount for &'a String { + fn char_count(&self) -> usize { + self.chars().count() + } +} + +impl<'a> HasCharCount for &'a str { + fn char_count(&self) -> usize { + self.chars().count() + } +} + +impl<'a> HasCharCount for std::borrow::Cow<'a, str> { + fn char_count(&self) -> usize { + self.chars().count() + } +} + +impl<'a, 'b> HasCharCount for &'a std::borrow::Cow<'b, str> { + fn char_count(&self) -> usize { + self.chars().count() + } +} + +impl HasCharCount for Box { + fn char_count(&self) -> usize { + self.chars().count() + } +} + +impl<'a> HasCharCount for &'a Box { + fn char_count(&self) -> usize { + self.chars().count() + } +} diff --git a/garde/src/rules/grapheme_count.rs b/garde/src/rules/grapheme_count.rs new file mode 100644 index 0000000..ac315c8 --- /dev/null +++ b/garde/src/rules/grapheme_count.rs @@ -0,0 +1,122 @@ +//! Grapheme count validation using the [`unicode_segmentation`] crate. +//! +//! ```rust +//! #[derive(garde::Validate)] +//! struct Test { +//! #[garde(grapheme_count(min=1, max=100))] +//! v: String, +//! } +//! ``` +//! +//! The entrypoint is the [`GraphemeCount`] trait. Implementing this trait for a type allows that type to be used with the `#[garde(grapheme_count(...))]` rule. +//! +//! Here's what implementing the trait for a custom string-like type might look like: +//! ```rust +//! use unicode_segmentation::UnicodeSegmentation; +//! +//! #[repr(transparent)] +//! struct MyString(String); +//! +//! impl garde::rules::grapheme_count::HasGraphemeCount for MyString { +//! fn grapheme_count(&self) -> usize { +//! self.0.graphemes(true).count() +//! } +//! } +//! ``` +//! + +use unicode_segmentation::UnicodeSegmentation; + +use crate::error::Error; + +pub fn apply(v: &T, (min, max): (usize, usize)) -> Result<(), Error> { + if let Err(e) = v.validate_grapheme_count(min, max) { + match e { + InvalidLength::Min => { + return Err(Error::new(format!("grapheme count is lower than {min}"))) + } + InvalidLength::Max => { + return Err(Error::new(format!("grapheme count is greater than {max}"))) + } + } + } + Ok(()) +} + +pub trait GraphemeCount { + fn validate_grapheme_count(&self, min: usize, max: usize) -> Result<(), InvalidLength>; +} + +pub enum InvalidLength { + Min, + Max, +} + +#[allow(clippy::len_without_is_empty)] +pub trait HasGraphemeCount { + fn grapheme_count(&self) -> usize; +} + +impl GraphemeCount for T { + fn validate_grapheme_count(&self, min: usize, max: usize) -> Result<(), InvalidLength> { + let len = HasGraphemeCount::grapheme_count(self); + if len < min { + Err(InvalidLength::Min) + } else if len > max { + Err(InvalidLength::Max) + } else { + Ok(()) + } + } +} + +impl GraphemeCount for Option { + fn validate_grapheme_count(&self, min: usize, max: usize) -> Result<(), InvalidLength> { + match self { + Some(value) => value.validate_grapheme_count(min, max), + None => Ok(()), + } + } +} + +impl HasGraphemeCount for String { + fn grapheme_count(&self) -> usize { + self.graphemes(true).count() + } +} + +impl<'a> HasGraphemeCount for &'a String { + fn grapheme_count(&self) -> usize { + self.graphemes(true).count() + } +} + +impl<'a> HasGraphemeCount for &'a str { + fn grapheme_count(&self) -> usize { + self.graphemes(true).count() + } +} + +impl<'a> HasGraphemeCount for std::borrow::Cow<'a, str> { + fn grapheme_count(&self) -> usize { + self.graphemes(true).count() + } +} + +impl<'a, 'b> HasGraphemeCount for &'a std::borrow::Cow<'b, str> { + fn grapheme_count(&self) -> usize { + self.graphemes(true).count() + } +} + +impl HasGraphemeCount for Box { + fn grapheme_count(&self) -> usize { + self.graphemes(true).count() + } +} + +impl<'a> HasGraphemeCount for &'a Box { + fn grapheme_count(&self) -> usize { + self.graphemes(true).count() + } +} diff --git a/garde/src/rules/length.rs b/garde/src/rules/length.rs index 447cd95..4dc419a 100644 --- a/garde/src/rules/length.rs +++ b/garde/src/rules/length.rs @@ -13,8 +13,8 @@ //! The [`Length`] has a companion trait [`HasLength`], which may be implemented for any container with a known length. //! [`Length`] is implemented for any `T: HasLength`. //! -//! In case of string types, [`HasLength::length`] should return the number of _characters_ as opposed to the number of _bytes_. -//! For validation of length counted in _bytes_, see the [`crate::rules::byte_length`] rule. +//! In case of string types, [`HasLength::length`] should return the number of _bytes_. +//! For validation of length counted in _graphemes_ or _USVs_, refer to the [`crate::rules::grapheme_count`] or [`crate::rules::char_count`] rule. //! //! Here's what implementing the trait for a custom string-like type might look like: //! ```rust @@ -23,7 +23,7 @@ //! //! impl garde::rules::length::HasLength for MyString { //! fn length(&self) -> usize { -//! self.0.chars().count() +//! self.0.len() //! } //! } //! ``` @@ -79,19 +79,19 @@ impl Length for Option { impl HasLength for String { fn length(&self) -> usize { - self.chars().count() + self.len() } } impl<'a> HasLength for &'a String { fn length(&self) -> usize { - self.chars().count() + self.len() } } impl<'a> HasLength for &'a str { fn length(&self) -> usize { - self.chars().count() + self.len() } } @@ -101,6 +101,24 @@ impl<'a> HasLength for std::borrow::Cow<'a, str> { } } +impl<'a, 'b> HasLength for &'a std::borrow::Cow<'b, str> { + fn length(&self) -> usize { + self.len() + } +} + +impl HasLength for Box { + fn length(&self) -> usize { + self.len() + } +} + +impl<'a> HasLength for &'a Box { + fn length(&self) -> usize { + self.len() + } +} + impl HasLength for Vec { fn length(&self) -> usize { self.len() diff --git a/garde/src/rules/mod.rs b/garde/src/rules/mod.rs index 79dc2e4..aa55a32 100644 --- a/garde/src/rules/mod.rs +++ b/garde/src/rules/mod.rs @@ -3,11 +3,14 @@ pub mod alphanumeric; pub mod ascii; pub mod byte_length; +pub mod char_count; pub mod contains; #[cfg(feature = "credit-card")] pub mod credit_card; #[cfg(feature = "email")] pub mod email; +#[cfg(feature = "unicode")] +pub mod grapheme_count; pub mod inner; pub mod ip; pub mod length; diff --git a/garde/tests/rules/byte_length.rs b/garde/tests/rules/byte_length.rs index 139259a..8cd613a 100644 --- a/garde/tests/rules/byte_length.rs +++ b/garde/tests/rules/byte_length.rs @@ -7,7 +7,7 @@ struct Test<'a> { #[garde(byte_length(min = 10, max = UWU - 1))] field: &'a str, - #[garde(inner(length(min = 10, max = 100)))] + #[garde(inner(byte_length(min = 10, max = 100)))] inner: &'a [&'a str], } diff --git a/garde/tests/rules/char_count.rs b/garde/tests/rules/char_count.rs new file mode 100644 index 0000000..ca0f4c7 --- /dev/null +++ b/garde/tests/rules/char_count.rs @@ -0,0 +1,131 @@ +use super::util; + +#[derive(Debug, garde::Validate)] +struct Test<'a> { + #[garde(char_count(min = 10, max = 100))] + field: &'a str, + #[garde(inner(char_count(min = 10, max = 100)))] + inner: &'a [&'a str], +} + +#[test] +fn char_count_valid() { + util::check_ok(&[ + Test { + // 'a' * 10 + field: "aaaaaaaaaa", + inner: &["aaaaaaaaaa"], + }, + Test { + // 'a' * 100 + field: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + inner: &["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], + }, + Test { + // "๐Ÿ˜‚" = 1 char + field: &"๐Ÿ˜‚".repeat(100), + inner: &[&"๐Ÿ˜‚".repeat(100)], + }, + Test { + // "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ" = 5 chars + // "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ" * 2 = 10 chars + field: &"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(2), + inner: &[&"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(2)], + }, + Test { + // "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ" = 5 chars + // "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ" * 20 = 100 chars + field: &"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(20), + inner: &[&"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(20)], + }, + ], &()) +} + +#[test] +fn char_count_invalid() { + util::check_fail!(&[ + Test { + // 'a' * 9 + field: "aaaaaaaaa", + inner: &["aaaaaaaaa"], + }, + Test { + // 'a' * 101 + field: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + inner: &["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], + }, + Test { + // "๐Ÿ˜‚" = 1 char + field: &"๐Ÿ˜‚".repeat(101), + inner: &[&"๐Ÿ˜‚".repeat(101)], + }, + Test { + // "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ" = 5 chars + field: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + inner: &["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ"], + }, + Test { + // "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ" = 5 chars + // "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ" * 21 = 105 chars + field: &"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(21), + inner: &[&"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(21)], + }, + ], &()) +} + +#[derive(Debug, garde::Validate)] +struct Exact<'a> { + #[garde(char_count(min = 5, max = 5))] + field: &'a str, + #[garde(inner(char_count(min = 5, max = 5)))] + inner: &'a [&'a str], +} + +#[test] +fn exact_char_count_valid() { + util::check_ok( + &[ + Exact { + // 'a' * 5 + field: "aaaaa", + inner: &["aaaaa"], + }, + Exact { + field: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + inner: &["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ"], + }, + ], + &(), + ) +} + +#[test] +fn exact_char_count_invalid() { + util::check_fail!( + &[ + Exact { + field: "", + inner: &[""], + }, + Exact { + // 'a' * 1 + field: "a", + inner: &["a"], + }, + Exact { + // 'a' * 3 + field: "aaa", + inner: &["aaa"], + }, + Exact { + field: "๐Ÿ˜‚", + inner: &["๐Ÿ˜‚"], + }, + Exact { + field: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + inner: &["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ"], + }, + ], + &() + ) +} diff --git a/garde/tests/rules/grapheme_count.rs b/garde/tests/rules/grapheme_count.rs new file mode 100644 index 0000000..cf576d8 --- /dev/null +++ b/garde/tests/rules/grapheme_count.rs @@ -0,0 +1,151 @@ +use super::util; + +#[derive(Debug, garde::Validate)] +struct Test<'a> { + #[garde(grapheme_count(min = 10, max = 100))] + field: String, + #[garde(inner(grapheme_count(min = 10, max = 100)))] + inner: &'a [String], +} + +#[test] +fn grapheme_count_valid() { + util::check_ok( + &[ + Test { + // 'a' = 1 grapheme + field: "a".repeat(10), + inner: &["a".repeat(10)], + }, + Test { + field: "a".repeat(100), + inner: &["a".repeat(100)], + }, + Test { + // '๐Ÿ˜‚' = 1 grapheme + field: "๐Ÿ˜‚".repeat(100), + inner: &["๐Ÿ˜‚".repeat(100)], + }, + Test { + // '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ' = 1 grapheme + field: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(10), + inner: &["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(10)], + }, + Test { + field: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(100), + inner: &["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(100)], + }, + Test { + // '๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ' = 2 graphemes + field: "๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(5), + inner: &["๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(5)], + }, + Test { + field: "๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(50), + inner: &["๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(50)], + }, + ], + &(), + ) +} + +#[test] +fn grapheme_count_invalid() { + util::check_fail!( + &[ + Test { + // 'a' = 1 grapheme + field: "a".repeat(9), + inner: &["a".repeat(9)], + }, + Test { + field: "a".repeat(101), + inner: &["a".repeat(101)], + }, + Test { + // '๐Ÿ˜‚' = 1 grapheme + field: "๐Ÿ˜‚".repeat(101), + inner: &["๐Ÿ˜‚".repeat(101)], + }, + Test { + // '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ' = 1 grapheme + field: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(9), + inner: &["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(9)], + }, + Test { + field: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(101), + inner: &["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(101)], + }, + Test { + // '๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ' = 2 graphemes + field: "๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(4), + inner: &["๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(4)], + }, + Test { + field: "๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(51), + inner: &["๐Ÿ˜‚๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(51)], + }, + ], + &() + ) +} + +#[derive(Debug, garde::Validate)] +struct Exact<'a> { + #[garde(grapheme_count(min = 5, max = 5))] + field: String, + #[garde(inner(grapheme_count(min = 5, max = 5)))] + inner: &'a [String], +} + +#[test] +fn exact_grapheme_count_valid() { + util::check_ok( + &[ + Exact { + field: "a".repeat(5), + inner: &["a".repeat(5)], + }, + Exact { + field: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(5), + inner: &["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ".repeat(5)], + }, + // 'ไฝ เคนเฅ‚เคืเจฒเฉ‹๐Ÿ˜‚' = 5 graphemes + Exact { + field: "ไฝ เคนเฅ‚เคืเจฒเฉ‹๐Ÿ˜‚".into(), + inner: &["ไฝ เคนเฅ‚เคืเจฒเฉ‹๐Ÿ˜‚".into()], + }, + ], + &(), + ) +} + +#[test] +fn exact_grapheme_count_invalid() { + util::check_fail!( + &[ + Exact { + field: "".into(), + inner: &["".into()], + }, + Exact { + field: "a".into(), + inner: &["a".into()], + }, + Exact { + field: "a".repeat(3), + inner: &["a".repeat(3)], + }, + Exact { + field: "๐Ÿ˜‚".repeat(4), + inner: &["๐Ÿ˜‚".repeat(4)], + }, + // 'ไฝ เคนเฅ‚เคืเจฒเฉ‹๐Ÿ˜‚ใ‚ข' = 6 graphemes + Exact { + field: "ไฝ เคนเฅ‚เคืเจฒเฉ‹๐Ÿ˜‚ใ‚ข".into(), + inner: &["ไฝ เคนเฅ‚เคืเจฒเฉ‹๐Ÿ˜‚ใ‚ข".into()], + }, + ], + &() + ) +} diff --git a/garde/tests/rules/length.rs b/garde/tests/rules/length.rs index d3245fe..d42fdeb 100644 --- a/garde/tests/rules/length.rs +++ b/garde/tests/rules/length.rs @@ -14,12 +14,18 @@ fn length_valid() { Test { // 'a' * 10 field: "aaaaaaaaaa", - inner: &["aaaaaaaaaa"] + inner: &["aaaaaaaaaa"], }, Test { // 'a' * 100 field: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - inner: &["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] + inner: &["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], + }, + Test { + // "๐Ÿ˜‚" = 4 bytes + // "๐Ÿ˜‚" * 25 = 100 bytes + field: "๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + inner: &["๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚"] }, ], &()) } @@ -30,32 +36,45 @@ fn length_invalid() { Test { // 'a' * 9 field: "aaaaaaaaa", - inner: &["aaaaaaaaa"] + inner: &["aaaaaaaaa"], }, Test { // 'a' * 101 field: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - inner: &["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] + inner: &["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], + }, + Test { + // "๐Ÿ˜‚" = 4 bytes + // 'a' * 1 + "๐Ÿ˜‚" * 25 = 101 bytes + field: "a๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + inner: &["a๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚"], }, ], &()) } #[derive(Debug, garde::Validate)] struct Exact<'a> { - #[garde(length(min = 2, max = 2))] + #[garde(length(min = 4, max = 4))] field: &'a str, - #[garde(inner(length(min = 2, max = 2)))] + #[garde(inner(length(min = 4, max = 4)))] inner: &'a [&'a str], } #[test] fn exact_length_valid() { util::check_ok( - &[Exact { - // 'a' * 2 - field: "aa", - inner: &["aa"], - }], + &[ + Exact { + // 'a' * 2 + field: "aaaa", + inner: &["aaaa"], + }, + Exact { + // '๐Ÿ˜‚' = 4 bytes + field: "๐Ÿ˜‚", + inner: &["๐Ÿ˜‚"], + }, + ], &(), ) } @@ -78,6 +97,11 @@ fn exact_length_invalid() { field: "aaa", inner: &["aaa"] }, + Exact { + // '๐Ÿ˜‚' * 2 = 8 + field: "๐Ÿ˜‚๐Ÿ˜‚", + inner: &["๐Ÿ˜‚๐Ÿ˜‚"] + }, ], &() ) diff --git a/garde/tests/rules/mod.rs b/garde/tests/rules/mod.rs index 4e968e6..6acc97e 100644 --- a/garde/tests/rules/mod.rs +++ b/garde/tests/rules/mod.rs @@ -2,12 +2,14 @@ mod allow_unvalidated; mod alphanumeric; mod ascii; mod byte_length; +mod char_count; mod contains; mod credit_card; mod custom; mod dive; mod dive_with_rules; mod email; +mod grapheme_count; mod inner; mod ip; mod length; diff --git a/garde/tests/rules/snapshots/rules__rules__byte_length__byte_length_invalid.snap b/garde/tests/rules/snapshots/rules__rules__byte_length__byte_length_invalid.snap index dfc5649..f7e7b2d 100644 --- a/garde/tests/rules/snapshots/rules__rules__byte_length__byte_length_invalid.snap +++ b/garde/tests/rules/snapshots/rules__rules__byte_length__byte_length_invalid.snap @@ -9,7 +9,7 @@ Test { ], } field: byte length is lower than 10 -inner[0]: length is lower than 10 +inner[0]: byte length is lower than 10 Test { field: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -18,7 +18,7 @@ Test { ], } field: byte length is greater than 100 -inner[0]: length is greater than 100 +inner[0]: byte length is greater than 100 Test { field: "a๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", @@ -27,5 +27,6 @@ Test { ], } field: byte length is greater than 100 +inner[0]: byte length is greater than 100 diff --git a/garde/tests/rules/snapshots/rules__rules__char_count__char_count_invalid.snap b/garde/tests/rules/snapshots/rules__rules__char_count__char_count_invalid.snap new file mode 100644 index 0000000..e17b1a2 --- /dev/null +++ b/garde/tests/rules/snapshots/rules__rules__char_count__char_count_invalid.snap @@ -0,0 +1,50 @@ +--- +source: garde/tests/./rules/char_count.rs +expression: snapshot +--- +Test { + field: "aaaaaaaaa", + inner: [ + "aaaaaaaaa", + ], +} +field: character count is lower than 10 +inner[0]: character count is lower than 10 + +Test { + field: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + inner: [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ], +} +field: character count is greater than 100 +inner[0]: character count is greater than 100 + +Test { + field: "๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + inner: [ + "๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + ], +} +field: character count is greater than 100 +inner[0]: character count is greater than 100 + +Test { + field: "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + inner: [ + "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + ], +} +field: character count is lower than 10 +inner[0]: character count is lower than 10 + +Test { + field: "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + inner: [ + "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + ], +} +field: character count is greater than 100 +inner[0]: character count is greater than 100 + + diff --git a/garde/tests/rules/snapshots/rules__rules__char_count__exact_char_count_invalid.snap b/garde/tests/rules/snapshots/rules__rules__char_count__exact_char_count_invalid.snap new file mode 100644 index 0000000..4fb5b7e --- /dev/null +++ b/garde/tests/rules/snapshots/rules__rules__char_count__exact_char_count_invalid.snap @@ -0,0 +1,50 @@ +--- +source: garde/tests/./rules/char_count.rs +expression: snapshot +--- +Exact { + field: "", + inner: [ + "", + ], +} +field: character count is lower than 5 +inner[0]: character count is lower than 5 + +Exact { + field: "a", + inner: [ + "a", + ], +} +field: character count is lower than 5 +inner[0]: character count is lower than 5 + +Exact { + field: "aaa", + inner: [ + "aaa", + ], +} +field: character count is lower than 5 +inner[0]: character count is lower than 5 + +Exact { + field: "๐Ÿ˜‚", + inner: [ + "๐Ÿ˜‚", + ], +} +field: character count is lower than 5 +inner[0]: character count is lower than 5 + +Exact { + field: "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + inner: [ + "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + ], +} +field: character count is greater than 5 +inner[0]: character count is greater than 5 + + diff --git a/garde/tests/rules/snapshots/rules__rules__grapheme_count__exact_grapheme_count_invalid.snap b/garde/tests/rules/snapshots/rules__rules__grapheme_count__exact_grapheme_count_invalid.snap new file mode 100644 index 0000000..64f5e79 --- /dev/null +++ b/garde/tests/rules/snapshots/rules__rules__grapheme_count__exact_grapheme_count_invalid.snap @@ -0,0 +1,50 @@ +--- +source: garde/tests/./rules/grapheme_count.rs +expression: snapshot +--- +Exact { + field: "", + inner: [ + "", + ], +} +field: grapheme count is lower than 5 +inner[0]: grapheme count is lower than 5 + +Exact { + field: "a", + inner: [ + "a", + ], +} +field: grapheme count is lower than 5 +inner[0]: grapheme count is lower than 5 + +Exact { + field: "aaa", + inner: [ + "aaa", + ], +} +field: grapheme count is lower than 5 +inner[0]: grapheme count is lower than 5 + +Exact { + field: "๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + inner: [ + "๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + ], +} +field: grapheme count is lower than 5 +inner[0]: grapheme count is lower than 5 + +Exact { + field: "ไฝ เคน\u{942}\u{901}ืเจฒ\u{a4b}๐Ÿ˜‚ใ‚ข", + inner: [ + "ไฝ เคน\u{942}\u{901}ืเจฒ\u{a4b}๐Ÿ˜‚ใ‚ข", + ], +} +field: grapheme count is greater than 5 +inner[0]: grapheme count is greater than 5 + + diff --git a/garde/tests/rules/snapshots/rules__rules__grapheme_count__grapheme_count_invalid.snap b/garde/tests/rules/snapshots/rules__rules__grapheme_count__grapheme_count_invalid.snap new file mode 100644 index 0000000..96d3e87 --- /dev/null +++ b/garde/tests/rules/snapshots/rules__rules__grapheme_count__grapheme_count_invalid.snap @@ -0,0 +1,68 @@ +--- +source: garde/tests/./rules/grapheme_count.rs +expression: snapshot +--- +Test { + field: "aaaaaaaaa", + inner: [ + "aaaaaaaaa", + ], +} +field: grapheme count is lower than 10 +inner[0]: grapheme count is lower than 10 + +Test { + field: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + inner: [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ], +} +field: grapheme count is greater than 100 +inner[0]: grapheme count is greater than 100 + +Test { + field: "๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + inner: [ + "๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + ], +} +field: grapheme count is greater than 100 +inner[0]: grapheme count is greater than 100 + +Test { + field: "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + inner: [ + "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + ], +} +field: grapheme count is lower than 10 +inner[0]: grapheme count is lower than 10 + +Test { + field: "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + inner: [ + "๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + ], +} +field: grapheme count is greater than 100 +inner[0]: grapheme count is greater than 100 + +Test { + field: "๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + inner: [ + "๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + ], +} +field: grapheme count is lower than 10 +inner[0]: grapheme count is lower than 10 + +Test { + field: "๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + inner: [ + "๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ๐Ÿ˜‚๐Ÿ‘จ\u{200d}๐Ÿ‘ฉ\u{200d}๐Ÿ‘ฆ", + ], +} +field: grapheme count is greater than 100 +inner[0]: grapheme count is greater than 100 + + diff --git a/garde/tests/rules/snapshots/rules__rules__length__exact_length_invalid.snap b/garde/tests/rules/snapshots/rules__rules__length__exact_length_invalid.snap index 3d415ae..0ddbfd4 100644 --- a/garde/tests/rules/snapshots/rules__rules__length__exact_length_invalid.snap +++ b/garde/tests/rules/snapshots/rules__rules__length__exact_length_invalid.snap @@ -8,8 +8,8 @@ Exact { "", ], } -field: length is lower than 2 -inner[0]: length is lower than 2 +field: length is lower than 4 +inner[0]: length is lower than 4 Exact { field: "a", @@ -17,8 +17,8 @@ Exact { "a", ], } -field: length is lower than 2 -inner[0]: length is lower than 2 +field: length is lower than 4 +inner[0]: length is lower than 4 Exact { field: "aaa", @@ -26,7 +26,16 @@ Exact { "aaa", ], } -field: length is greater than 2 -inner[0]: length is greater than 2 +field: length is lower than 4 +inner[0]: length is lower than 4 + +Exact { + field: "๐Ÿ˜‚๐Ÿ˜‚", + inner: [ + "๐Ÿ˜‚๐Ÿ˜‚", + ], +} +field: length is greater than 4 +inner[0]: length is greater than 4 diff --git a/garde/tests/rules/snapshots/rules__rules__length__length_invalid.snap b/garde/tests/rules/snapshots/rules__rules__length__length_invalid.snap index ed4c7a9..cecb959 100644 --- a/garde/tests/rules/snapshots/rules__rules__length__length_invalid.snap +++ b/garde/tests/rules/snapshots/rules__rules__length__length_invalid.snap @@ -20,4 +20,13 @@ Test { field: length is greater than 100 inner[0]: length is greater than 100 +Test { + field: "a๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + inner: [ + "a๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚", + ], +} +field: length is greater than 100 +inner[0]: length is greater than 100 + diff --git a/garde/tests/ui/compile-pass/char_count.rs b/garde/tests/ui/compile-pass/char_count.rs new file mode 100644 index 0000000..d65994f --- /dev/null +++ b/garde/tests/ui/compile-pass/char_count.rs @@ -0,0 +1,11 @@ +#[derive(garde::Validate)] +struct Test<'a> { + #[garde(char_count(min = 10, max = 100))] + field: &'a str, + #[garde(char_count(min = 10, max = 10))] + field2: &'a str, + #[garde(inner(char_count(min = 10, max = 10)))] + inner: &'a [&'a str], +} + +fn main() {} diff --git a/garde/tests/ui/compile-pass/grapheme_count.rs b/garde/tests/ui/compile-pass/grapheme_count.rs new file mode 100644 index 0000000..d4d56c4 --- /dev/null +++ b/garde/tests/ui/compile-pass/grapheme_count.rs @@ -0,0 +1,11 @@ +#[derive(garde::Validate)] +struct Test<'a> { + #[garde(grapheme_count(min = 10, max = 100))] + field: &'a str, + #[garde(grapheme_count(min = 10, max = 10))] + field2: &'a str, + #[garde(inner(grapheme_count(min = 10, max = 10)))] + inner: &'a [&'a str], +} + +fn main() {} diff --git a/garde_derive/src/check.rs b/garde_derive/src/check.rs index a831e9b..6ac2ba7 100644 --- a/garde_derive/src/check.rs +++ b/garde_derive/src/check.rs @@ -351,6 +351,8 @@ fn check_rule( PhoneNumber => apply!(rule_set, PhoneNumber(), span), Length(v) => apply!(rule_set, Length(check_range_generic(v)?), span), ByteLength(v) => apply!(rule_set, ByteLength(check_range_generic(v)?), span), + CharCount(v) => apply!(rule_set, CharCount(check_range_generic(v)?), span), + GraphemeCount(v) => apply!(rule_set, GraphemeCount(check_range_generic(v)?), span), Range(v) => apply!(rule_set, Range(check_range_not_ord(v)?), span), Contains(v) => apply!(rule_set, Contains(v), span), Prefix(v) => apply!(rule_set, Prefix(v), span), diff --git a/garde_derive/src/check_deprecated.rs b/garde_derive/src/check_deprecated.rs new file mode 100644 index 0000000..4a26a74 --- /dev/null +++ b/garde_derive/src/check_deprecated.rs @@ -0,0 +1,85 @@ +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::{quote_spanned, ToTokens}; + +use crate::model::{self, RawRule}; + +const BYTE_LENGTH_DEPRECATED: &str = "the `byte_length` attribute is deprecated. Use `length` instead. (See https://github.com/jprochazk/garde/issues/84)"; + +pub struct DeprecatedWarningSpans { + input_ident_name: String, + spans: Vec, +} + +pub fn check(input: &model::Input) -> DeprecatedWarningSpans { + let model::Input { ident, kind, .. } = input; + + let mut spans = Vec::new(); + match kind { + model::InputKind::Struct(variant) => { + check_variant(variant, &mut spans); + } + model::InputKind::Enum(list) => { + for (_, variant) in list { + if let Some(variant) = variant { + check_variant(variant, &mut spans); + } + } + } + }; + + DeprecatedWarningSpans { + input_ident_name: ident.to_string(), + spans, + } +} + +fn check_variant(variant: &model::Variant, spans: &mut Vec) { + match variant { + model::Variant::Struct(map) => { + for field in map.values() { + check_field(field, spans); + } + } + model::Variant::Tuple(list) => { + for field in list { + check_field(field, spans); + } + } + } +} + +fn check_field(field: &model::Field, spans: &mut Vec) { + let model::Field { + rules: raw_rules, .. + } = field; + + for RawRule { span, kind } in raw_rules { + if let model::RawRuleKind::ByteLength(_) = kind { + spans.push(*span); + } + } +} + +impl ToTokens for DeprecatedWarningSpans { + fn to_tokens(&self, tokens: &mut TokenStream2) { + for (i, &span) in self.spans.iter().enumerate() { + let name = Ident::new( + &format!("__{}_byte_length_deprecated_{i}", self.input_ident_name), + Span::call_site(), + ); + + // Inspired by: + // https://github.com/ggwpez/proc-macro-warning/blob/970809f551eb78ea003006ef4da0c303ede8501d/proc-macro-warning/src/lib.rs#L260 + quote_spanned! {span=> + #[allow(dead_code)] + fn #name() { + #[deprecated(note = #BYTE_LENGTH_DEPRECATED)] + #[allow(non_upper_case_globals)] + const _w: () = (); + let _ = _w; + } + } + .to_tokens(tokens); + } + } +} diff --git a/garde_derive/src/emit.rs b/garde_derive/src/emit.rs index 8522343..cf6681a 100644 --- a/garde_derive/src/emit.rs +++ b/garde_derive/src/emit.rs @@ -253,11 +253,13 @@ impl<'a> ToTokens for Rules<'a> { IpV6 => { quote!((::garde::rules::ip::IpKind::V6,)) } - Length(range) | ByteLength(range) => match range { - model::ValidateRange::GreaterThan(min) => quote!((#min, usize::MAX)), - model::ValidateRange::LowerThan(max) => quote!((0usize, #max)), - model::ValidateRange::Between(min, max) => quote!((#min, #max)), - }, + Length(range) | ByteLength(range) | CharCount(range) | GraphemeCount(range) => { + match range { + model::ValidateRange::GreaterThan(min) => quote!((#min, usize::MAX)), + model::ValidateRange::LowerThan(max) => quote!((0usize, #max)), + model::ValidateRange::Between(min, max) => quote!((#min, #max)), + } + } Range(range) => match range { model::ValidateRange::GreaterThan(min) => quote!((Some(#min), None)), model::ValidateRange::LowerThan(max) => quote!((None, Some(#max))), diff --git a/garde_derive/src/lib.rs b/garde_derive/src/lib.rs index e2106ec..053e7c2 100644 --- a/garde_derive/src/lib.rs +++ b/garde_derive/src/lib.rs @@ -1,11 +1,12 @@ mod check; +mod check_deprecated; mod emit; mod model; mod syntax; mod util; use proc_macro::{Delimiter, Literal, Span, TokenStream, TokenTree}; -use quote::quote; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::DeriveInput; #[proc_macro_derive(Validate, attributes(garde))] @@ -15,11 +16,17 @@ pub fn derive_validate(input: TokenStream) -> TokenStream { Ok(v) => v, Err(e) => return e.into_compile_error().into(), }; + + let deprecated = check_deprecated::check(&input); + let input = match check::check(input) { Ok(v) => v, Err(e) => return e.into_compile_error().into(), }; - emit::emit(input).into() + + let mut stream = emit::emit(input); + stream.append_all(deprecated.into_token_stream()); + stream.into() } #[proc_macro] diff --git a/garde_derive/src/model.rs b/garde_derive/src/model.rs index 1ca0cbc..4e0e3a0 100644 --- a/garde_derive/src/model.rs +++ b/garde_derive/src/model.rs @@ -90,6 +90,8 @@ pub enum RawRuleKind { PhoneNumber, Length(Range>), ByteLength(Range>), + CharCount(Range>), + GraphemeCount(Range>), Range(Range), Contains(Expr), Prefix(Expr), @@ -220,6 +222,8 @@ pub enum ValidateRule { PhoneNumber, Length(ValidateRange>), ByteLength(ValidateRange>), + CharCount(ValidateRange>), + GraphemeCount(ValidateRange>), Range(ValidateRange), Contains(Expr), Prefix(Expr), @@ -242,6 +246,8 @@ impl ValidateRule { ValidateRule::PhoneNumber => "phone_number", ValidateRule::Length { .. } => "length", ValidateRule::ByteLength { .. } => "byte_length", + ValidateRule::CharCount { .. } => "char_count", + ValidateRule::GraphemeCount { .. } => "grapheme_count", ValidateRule::Range { .. } => "range", ValidateRule::Contains(_) => "contains", ValidateRule::Prefix(_) => "prefix", diff --git a/garde_derive/src/syntax.rs b/garde_derive/src/syntax.rs index b23f8b7..add2788 100644 --- a/garde_derive/src/syntax.rs +++ b/garde_derive/src/syntax.rs @@ -290,6 +290,8 @@ impl Parse for model::RawRule { "phone_number" => PhoneNumber, "length" => Length(content), "byte_length" => ByteLength(content), + "char_count" => CharCount(content), + "grapheme_count" => GraphemeCount(content), "range" => Range(content), "contains" => Contains(content), "prefix" => Prefix(content), diff --git a/xtask/Cargo.lock b/xtask/Cargo.lock deleted file mode 100644 index 928b323..0000000 --- a/xtask/Cargo.lock +++ /dev/null @@ -1,290 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "anyhow" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" -dependencies = [ - "backtrace", -] - -[[package]] -name = "argp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c16c577a1a3b720a90eb2127bd0ae61530a71064d1a6babaaaa87f6174b9f1" -dependencies = [ - "argp_derive", -] - -[[package]] -name = "argp_derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3763c8b5e0ef2f7d0df26daa671808cc75e2d81547f63ccca96bf045e41799" -dependencies = [ - "proc-macro2", - "pulldown-cmark", - "quote", - "syn", -] - -[[package]] -name = "backtrace" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "cc" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "gimli" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "kstring" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" -dependencies = [ - "static_assertions", -] - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "object" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" -dependencies = [ - "memchr", -] - -[[package]] -name = "proc-macro2" -version = "1.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pulldown-cmark" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" -dependencies = [ - "bitflags", - "getopts", - "memchr", - "unicase", -] - -[[package]] -name = "quote" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "semver" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" - -[[package]] -name = "toml_edit" -version = "0.19.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" -dependencies = [ - "indexmap", - "kstring", - "toml_datetime", - "winnow", -] - -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-ident" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "winnow" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" -dependencies = [ - "memchr", -] - -[[package]] -name = "xtask" -version = "0.1.0" -dependencies = [ - "anyhow", - "argp", - "glob", - "semver", - "toml_edit", -] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 3e06487..f0fd1de 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -11,10 +11,10 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = { version = "1.0.72", features = ["backtrace"] } +anyhow = { version = "1.0", features = ["backtrace"] } argp = "0.3" -glob = { version = "0.3.1", default-features = false } -semver = "1.0.18" -toml_edit = { version = "0.19.14", features = ["perf"] } +glob = { version = "0.3", default-features = false } +semver = "1.0" +toml_edit = { version = "0.21", features = ["perf"] } [workspace]