Skip to content

Commit

Permalink
Merge pull request #67 from jprochazk/flat-error-repr
Browse files Browse the repository at this point in the history
Flat error representation
  • Loading branch information
jprochazk authored Sep 2, 2023
2 parents 7ad6f59 + ceed749 commit 042fbc6
Show file tree
Hide file tree
Showing 42 changed files with 1,002 additions and 673 deletions.
24 changes: 10 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,16 @@ struct MyVec<T>(Vec<T>);
impl<T: garde::Validate> garde::Validate for MyVec<T> {
type Context = T::Context;

fn validate(&self, ctx: &Self::Context) -> Result<(), garde::Errors> {
garde::Errors::list(|errors| {
for item in self.0.iter() {
errors.push(item.validate(ctx));
}
})
.finish()
fn validate_into(
&self,
ctx: &Self::Context,
mut parent: &mut dyn FnMut() -> garde::Path,
report: &mut garde::Report
) {
for (index, item) in self.0.iter().enumerate() {
let mut path = garde::util::nested_path!(parent, index);
item.validate_into(ctx, &mut path, report);
}
}
}

Expand All @@ -261,13 +264,6 @@ struct Bar {
}
```

To make implementing the trait easier, the `Errors` type supports a nesting builders.
- For list-like or tuple-like data structures, use `Errors::list`, and its `.push` method to attach nested `Errors`.
- For map-like data structures, use `Errors::fields`, and its `.insert` method to attach nested `Errors`.
- For a "flat" error list, use `Errors::simple`, and its `.push` method to attach individual errors.

The `ListErrorBuilder::push` and `ListErrorBuilder::insert` methods will ignore any errors which are empty (via `Errors::is_empty`).

### Integration with web frameworks

- [`axum`](https://crates.io/crates/axum): https://crates.io/crates/axum_garde
Expand Down
21 changes: 18 additions & 3 deletions garde/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,29 @@ default = [
"email-idna",
"regex",
]
serde = ["dep:serde"]
serde = ["dep:serde", "compact_str/serde"]
derive = ["dep:garde_derive"]
url = ["dep:url"]
credit-card = ["dep:card-validate"]
phone-number = ["dep:phonenumber"]
email = ["regex"]
email-idna = ["dep:idna"]
regex = ["dep:regex", "dep:once_cell", "garde_derive?/regex"]
pattern = ["regex"] # for backward compatibility with <0.14.0
pattern = ["regex"] # for backward compatibility with <0.14.0

[dependencies]
garde_derive = { version = "0.14.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 }

serde = { version = "1", features = ["derive"], optional = true }
url = { version = "2", optional = true }
card-validate = { version = "2.3", optional = true }
phonenumber = { version = "0.3.2+8.13.9", optional = true }
regex = { version = "1", default-features = false, features = ["std"], optional = true }
regex = { version = "1", default-features = false, features = [
"std",
], optional = true }
once_cell = { version = "1", optional = true }
idna = { version = "0.3", optional = true }

Expand All @@ -47,3 +52,13 @@ trybuild = { version = "1.0" }
insta = { version = "1.29" }
owo-colors = { version = "3.5.0" }
glob = "0.3.1"

criterion = "0.4"

[[bench]]
name = "validation"
harness = false

[profile.profiling]
inherits = "release"
debug = true
178 changes: 178 additions & 0 deletions garde/benches/validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use garde::Validate;

#[derive(Debug, garde::Validate)]
struct Test<'a> {
#[garde(alphanumeric)]
alphanumeric: Option<&'a str>,
#[garde(ascii)]
ascii: Option<&'a str>,
#[garde(byte_length(min = 1))]
byte_length_min1_str: Option<&'a str>,
#[garde(byte_length(min = 1))]
byte_length_min1_u8_slice: Option<&'a [u8]>,
#[garde(contains("a"))]
contains_a: Option<&'a str>,
#[garde(credit_card)]
credit_card: Option<&'a str>,
#[garde(email)]
email: Option<&'a str>,
#[garde(ip)]
ip: Option<&'a str>,
#[garde(length(min = 1))]
length_min1: Option<&'a str>,
#[garde(pattern(r"a|b"))]
pat_a_or_b: Option<&'a str>,
#[garde(phone_number)]
phone_number: Option<&'a str>,
#[garde(prefix("a"))]
prefix_a: Option<&'a str>,
#[garde(range(min = 1))]
range_min1: Option<i32>,
#[garde(required)]
required: Option<&'a str>,
#[garde(suffix("a"))]
suffix_a: Option<&'a str>,
#[garde(url)]
url: Option<&'a str>,
#[garde(dive)]
nested: Option<Box<Test<'a>>>,
}

macro_rules! valid_input {
() => {
Test {
alphanumeric: Some("a"),
ascii: Some("a"),
byte_length_min1_str: Some("a"),
byte_length_min1_u8_slice: Some(&[0]),
contains_a: Some("a"),
credit_card: Some("4539571147647251"),
email: Some("[email protected]"),
ip: Some("127.0.0.1"),
length_min1: Some("a"),
pat_a_or_b: Some("a"),
phone_number: Some("+14152370800"),
prefix_a: Some("a"),
range_min1: Some(1),
required: Some("a"),
suffix_a: Some("a"),
url: Some("http://test.com"),
nested: None,
}
};
($nested:expr) => {
Test {
alphanumeric: Some("a"),
ascii: Some("a"),
byte_length_min1_str: Some("a"),
byte_length_min1_u8_slice: Some(&[0]),
contains_a: Some("a"),
credit_card: Some("4539571147647251"),
email: Some("[email protected]"),
ip: Some("127.0.0.1"),
length_min1: Some("a"),
pat_a_or_b: Some("a"),
phone_number: Some("+14152370800"),
prefix_a: Some("a"),
range_min1: Some(1),
required: Some("a"),
suffix_a: Some("a"),
url: Some("http://test.com"),
nested: Some(Box::new($nested)),
}
};
}

macro_rules! invalid_input {
() => {
Test {
alphanumeric: Some("πŸ˜‚"),
ascii: Some("πŸ˜‚"),
byte_length_min1_str: Some(""),
byte_length_min1_u8_slice: Some(&[]),
contains_a: Some("πŸ˜‚"),
credit_card: Some("πŸ˜‚"),
email: Some("πŸ˜‚"),
ip: Some("πŸ˜‚"),
length_min1: Some(""),
pat_a_or_b: Some("πŸ˜‚"),
phone_number: Some("πŸ˜‚"),
prefix_a: Some(""),
range_min1: Some(0),
required: None,
suffix_a: Some("πŸ˜‚"),
url: Some("πŸ˜‚"),
nested: None,
}
};
($nested:expr) => {
Test {
alphanumeric: Some("πŸ˜‚"),
ascii: Some("πŸ˜‚"),
byte_length_min1_str: Some(""),
byte_length_min1_u8_slice: Some(&[]),
contains_a: Some("πŸ˜‚"),
credit_card: Some("πŸ˜‚"),
email: Some("πŸ˜‚"),
ip: Some("πŸ˜‚"),
length_min1: Some(""),
pat_a_or_b: Some("πŸ˜‚"),
phone_number: Some("πŸ˜‚"),
prefix_a: Some(""),
range_min1: Some(0),
required: None,
suffix_a: Some("πŸ˜‚"),
url: Some("πŸ˜‚"),
nested: Some(Box::new($nested)),
}
};
}

fn validate(c: &mut Criterion) {
let inputs = vec![
(
"valid",
valid_input!(valid_input!(valid_input!(valid_input!()))),
),
(
"invalid",
invalid_input!(invalid_input!(invalid_input!(invalid_input!()))),
),
];

for (name, input) in inputs {
c.bench_function(&format!("validate `{name}`"), |b| {
b.iter(|| {
let _ = black_box(input.validate(&()));
})
});
}
}

fn display(c: &mut Criterion) {
let inputs = vec![
(
"valid",
valid_input!(valid_input!(valid_input!(valid_input!()))).validate(&()),
),
(
"invalid",
invalid_input!(invalid_input!(invalid_input!(invalid_input!()))).validate(&()),
),
];

for (name, input) in inputs {
c.bench_function(&format!("display `{name}`"), |b| {
b.iter(|| {
let _ = black_box(match &input {
Ok(()) => String::new(),
Err(e) => e.to_string(),
});
})
});
}
}

criterion_group!(benches, validate, display);
criterion_main!(benches);
Loading

0 comments on commit 042fbc6

Please sign in to comment.