Skip to content

Commit

Permalink
Merge pull request #122 from Rolv-Apneseth/support-equal-for-length
Browse files Browse the repository at this point in the history
  • Loading branch information
jprochazk authored Aug 3, 2024
2 parents e3ac34b + c677b03 commit be00ddd
Show file tree
Hide file tree
Showing 17 changed files with 397 additions and 54 deletions.
52 changes: 27 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,35 +80,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(<mode>, min=<usize>, max=<usize>)]` | a container with length in `min..=max` | - |
| matches | `#[garde(matches(<field>))]` | a field matches another field | - |
| range | `#[garde(range(min=<expr>, max=<expr>))]` | a number in the range `min..=max` | - |
| contains | `#[garde(contains(<string>))]` | a string-like value containing a substring | - |
| prefix | `#[garde(prefix(<string>))]` | a string-like value prefixed by some string | - |
| suffix | `#[garde(suffix(<string>))]` | a string-like value suffixed by some string | - |
| pattern | `#[garde(pattern("<regex>"))]` | a string-like value matching some regular expression | `regex` |
| pattern | `#[garde(pattern(<matcher>))]` | 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(<function or closure>))]` | 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(<mode>, min=<usize>, max=<usize>, equal=<usize>)]` | a container with length in `min..=max` or `equal` | - |
| matches | `#[garde(matches(<field>))]` | a field matches another field | - |
| range | `#[garde(range(min=<expr>, max=<expr>, equal=<expr>))]` | a number in the range `min..=max` or `equal` | - |
| contains | `#[garde(contains(<string>))]` | a string-like value containing a substring | - |
| prefix | `#[garde(prefix(<string>))]` | a string-like value prefixed by some string | - |
| suffix | `#[garde(suffix(<string>))]` | a string-like value suffixed by some string | - |
| pattern | `#[garde(pattern("<regex>"))]` | a string-like value matching some regular expression | `regex` |
| pattern | `#[garde(pattern(<matcher>))]` | 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(<function or closure>))]` | 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`).
- The `<mode>` argument for `length` is [explained here](#length-modes)
- For `length` and `range`:
- If `equal` is defined, `min` and `max` must be omitted.
- Assuming `equal` is omitted, either `min` or `max` may be omitted, but not both.
- `min` and `max` use an *inclusive* upper bound (`min..=max`). Setting `min == max` is equivalent to using `equal`.
- 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`.

Expand Down
47 changes: 45 additions & 2 deletions garde/tests/rules/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ fn length_invalid() {

#[derive(Debug, garde::Validate)]
struct Exact<'a> {
#[garde(length(min = 2, max = 2))]
#[garde(length(equal = 2))]
field: &'a str,
#[garde(inner(length(min = 2, max = 2)))]
#[garde(inner(length(equal = 2)))]
inner: &'a [&'a str],
}

Expand Down Expand Up @@ -83,6 +83,49 @@ fn exact_length_invalid() {
)
}

#[derive(Debug, garde::Validate)]
struct MinMaxEqual<'a> {
#[garde(length(min = 2, max = 2))]
min_max: &'a str,
#[garde(length(equal = 2))]
equal: &'a str,
}

#[test]
fn min_max_equal_length_valid() {
util::check_ok(
&[MinMaxEqual {
// 'b' * 2
min_max: "bb",
equal: "bb",
}],
&(),
)
}

#[test]
fn min_max_equal_length_invalid() {
util::check_fail!(
&[
MinMaxEqual {
min_max: "",
equal: ""
},
MinMaxEqual {
// 'b' * 1
min_max: "b",
equal: "b"
},
MinMaxEqual {
// 'b' * 3
min_max: "bbb",
equal: "bbb"
},
],
&()
)
}

#[derive(Debug, garde::Validate)]
struct SpecialLengthTest<'a> {
#[garde(length(simple, max = 1))]
Expand Down
81 changes: 81 additions & 0 deletions garde/tests/rules/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,84 @@ fn range_invalid() {
&()
)
}

#[derive(Debug, garde::Validate)]
struct Exact<'a> {
#[garde(range(equal = 2))]
field: u64,
#[garde(inner(range(equal = 2)))]
inner: &'a [i32],
}

#[test]
fn exact_length_valid() {
util::check_ok(
&[Exact {
field: 2,
inner: &[2],
}],
&(),
)
}

#[test]
fn exact_length_invalid() {
util::check_fail!(
&[
Exact {
field: 0,
inner: &[0]
},
Exact {
field: 1,
inner: &[1]
},
Exact {
// 'a' * 3
field: 3,
inner: &[3]
},
],
&()
)
}

#[derive(Debug, garde::Validate)]
struct MinMaxEqual {
#[garde(range(min = 40, max = 40))]
min_max: u64,
#[garde(range(equal = 40))]
equal: u64,
}

#[test]
fn min_max_equal_length_valid() {
util::check_ok(
&[MinMaxEqual {
min_max: 40,
equal: 40,
}],
&(),
)
}

#[test]
fn min_max_equal_length_invalid() {
util::check_fail!(
&[
MinMaxEqual {
min_max: 0,
equal: 0
},
MinMaxEqual {
min_max: 39,
equal: 39
},
MinMaxEqual {
min_max: 41,
equal: 41
},
],
&()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
source: garde/tests/./rules/length.rs
expression: snapshot
---
MinMaxEqual {
min_max: "",
equal: "",
}
equal: length is lower than 2
min_max: length is lower than 2

MinMaxEqual {
min_max: "b",
equal: "b",
}
equal: length is lower than 2
min_max: length is lower than 2

MinMaxEqual {
min_max: "bbb",
equal: "bbb",
}
equal: length is greater than 2
min_max: length is greater than 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
source: garde/tests/./rules/range.rs
expression: snapshot
---
Exact {
field: 0,
inner: [
0,
],
}
field: lower than 2
inner[0]: lower than 2

Exact {
field: 1,
inner: [
1,
],
}
field: lower than 2
inner[0]: lower than 2

Exact {
field: 3,
inner: [
3,
],
}
field: greater than 2
inner[0]: greater than 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
source: garde/tests/./rules/range.rs
expression: snapshot
---
MinMaxEqual {
min_max: 0,
equal: 0,
}
equal: lower than 40
min_max: lower than 40

MinMaxEqual {
min_max: 39,
equal: 39,
}
equal: lower than 40
min_max: lower than 40

MinMaxEqual {
min_max: 41,
equal: 41,
}
equal: greater than 40
min_max: greater than 40
9 changes: 9 additions & 0 deletions garde/tests/ui/compile-fail/length_bad_equal_combined_max.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![allow(dead_code)]

#[derive(garde::Validate)]
struct Test<'a> {
#[garde(length(max = 1, equal = 10))]
max_equal: &'a str,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: no `min` or `max` allowed if using `equal`
--> tests/ui/compile-fail/length_bad_equal_combined_max.rs
|
| #[garde(length(max = 1, equal = 10))]
| ^^^
9 changes: 9 additions & 0 deletions garde/tests/ui/compile-fail/length_bad_equal_combined_min.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![allow(dead_code)]

#[derive(garde::Validate)]
struct Test<'a> {
#[garde(length(min = 1, equal = 10))]
min_equal: &'a str,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: no `min` or `max` allowed if using `equal`
--> tests/ui/compile-fail/length_bad_equal_combined_min.rs
|
| #[garde(length(min = 1, equal = 10))]
| ^^^
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![allow(dead_code)]

#[derive(garde::Validate)]
struct Test<'a> {
#[garde(length(min = 1, max = 1, equal = 10))]
min_max_equal: &'a str,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: no `min` or `max` allowed if using `equal`
--> tests/ui/compile-fail/length_bad_equal_combined_min_max.rs
|
| #[garde(length(min = 1, max = 1, equal = 10))]
| ^^^
18 changes: 11 additions & 7 deletions garde/tests/ui/compile-pass/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@
struct Test<'a> {
#[garde(length(min = 10, max = 100))]
field: &'a str,
#[garde(length(min = 10, max = 10))]
#[garde(length(equal = 10))]
field2: &'a str,
#[garde(length(min = 10, max = 10))]
field3: &'a str,
#[garde(inner(length(min = 10, max = 100)))]
inner: &'a [&'a str],
#[garde(inner(length(min = 10, max = 10)))]
#[garde(inner(length(equal = 10)))]
inner2: &'a [&'a str],
#[garde(inner(length(min = 10, max = 10)))]
inner3: &'a [&'a str],

#[garde(length(simple, min = 1, max = 1))]
#[garde(length(simple, equal = 1))]
simple: &'a str,
#[garde(length(bytes, min = 1, max = 1))]
#[garde(length(bytes, equal = 1))]
bytes: &'a str,
#[garde(length(chars, min = 1, max = 1))]
#[garde(length(chars, equal = 1))]
chars: &'a str,
#[garde(length(graphemes, min = 1, max = 1))]
#[garde(length(graphemes, equal = 1))]
graphemes: &'a str,
#[garde(length(utf16, min = 1, max = 1))]
#[garde(length(utf16, equal = 1))]
utf16: &'a str,
}

Expand Down
Loading

0 comments on commit be00ddd

Please sign in to comment.