Closed abedra closed 3 months ago
~With 0.18?~ Nevermind, saw it in the crate path It shouldn't be too hard to fix if someone wants to try but wee need to add a test first
Hmm, the logic in merge_self
seems very strange. For every ValidationErrorsKind::Field
it's called with, it tries to add all of the errors, so if you have two failing fields, it tries to add both fields twice.
It seems like different parts of the library expects it to function in very different ways though, and since it doesn't have any comments it's not clear what's the right way to use it. Based on the function signatures I'd expect merge
and merge_self
to do the same thing and just differ in whether it mutates self
or takes ownership of the parent, but they behave completely differently, hmm..
The plan for the next version is to completely revamp the error system since tbh I don't understand it anymore but it would be nice to fix it temporarily.
Say you have the following test case for example:
#[test]
fn foo() {
#[derive(Validate)]
struct Root<'a> {
#[validate(length(min = 5, max = 10))]
value: String,
#[validate(nested)]
a: &'a A,
}
#[derive(Validate)]
struct A {
#[validate(length(min = 5, max = 10))]
value1: String,
#[validate(length(min = 5, max = 10))]
value2: String,
}
let root = Root {
value: "valid".to_string(),
a: &A { value1: "invalid value".to_string(), value2: "invalid value".to_string() },
};
assert!(root.validate().is_err());
}
This ends up calling merge_self
with
[validator/src/types.rs:73:9] &field = "a"
[validator/src/types.rs:73:9] &child = Err(
ValidationErrors(
{
"value2": Field(
[
ValidationError {
code: "length",
message: None,
params: {
"min": Number(5),
"max": Number(10),
"value": String("invalid value"),
},
},
],
),
"value1": Field(
[
ValidationError {
code: "length",
message: None,
params: {
"max": Number(10),
"value": String("invalid value"),
"min": Number(5),
},
},
],
),
},
),
)
This is of course simple enough to do by just calling add_self
as per merge
. But this completely breaks how the collections are implemented.
If we instead have the following test case:
#[test]
fn foo() {
#[derive(Validate)]
struct Root<'a> {
#[validate(length(min = 5, max = 10))]
value: String,
#[validate(nested)]
a: &'a [A],
}
#[derive(Validate)]
struct A {
#[validate(length(min = 5, max = 10))]
value: String,
}
let root = Root {
value: "valid".to_string(),
a: &[A { value: "invalid value".to_string() }, A { value: "invalid value".to_string() }],
};
assert!(root.validate().is_err());
}
then merge_self
gets called with
[validator/src/types.rs:73:9] &field = "a"
[validator/src/types.rs:73:9] &child = Err(
ValidationErrors(
{
"_tmp_validator": List(
{
0: ValidationErrors(
{
"value": Field(
[
ValidationError {
code: "length",
message: None,
params: {
"max": Number(10),
"value": String("invalid value"),
"min": Number(5),
},
},
],
),
},
),
1: ValidationErrors(
{
"value": Field(
[
ValidationError {
code: "length",
message: None,
params: {
"min": Number(5),
"value": String("invalid value"),
"max": Number(10),
},
},
],
),
},
),
},
),
},
),
)
Because the collection validation calls return a ValidationErrors
with a fake name ("_tmpvalidator"), and then we're expected to strip off that outer part, which we're not expected to do for non-collection nested validation.
I'm honestly not quite sure of how to fix this. I guess we'd have to explicitly check for the presence of _tmp_validator
, but that's rather ugly? :/
Welp, I guess in typing all of that out I figured out the only way to fix it with the way the types and collection support currently works. It's definitely ugly, but on the other hand I'd argue that [...].validate()
returning something containing a _tmp_validator
entry is also pretty ugly, so what can we do.
Say I've got the following definition
and the following definition for
ControlStrength
Writing a test to validate the failures causes the following error
If I'm reading things correctly, it's trying to replace the error for the field instead of accumulating errors for the field.