Serialize constraints
It would be useful to support getting a list of constraints on a struct and serializing them to JSON. A possible use-case is sharing the validations between the backend and frontend of a web app without having to manually keep them in sync.
#[derive(Validate)]
struct User {
#[garde(required)]
first_name: String,
#[garde(required)]
last_name: String,
#[garde(length(max=100))]
bio: String,
}
let constraints = <User as garde::Constraints>::constraints();
let serialized = serde_json::to_string(&constraints).unwrap();
{
"first_name": [{"type":"required"}],
"last_name": [{"type":"required"}],
"bio": [{"type":"length","params":{"max":100}}]
}
Error messages should also support serde::Serialize. Some inspiration could be taken from similar libraries in other ecosystems, such as zod for TypeScript.
The output should respect rename.
Serializing error reports was implemented in https://github.com/jprochazk/garde/commit/a989bcbfd28a4d7aa2c79ac936ee5c19ac857f25. All that's missing is serializing constraints, which shouldn't be too difficult to implement:
- It should be emitted as part of the
Validatederive - It should be a straightforward translation of the model.
For example:
#[derive(garde::Validate)]
struct Foo {
#[garde(length(min = 1))]
s: String,
}
Would derive the following:
impl garde::Validate for Foo {
// ... the usual validate impl
fn constraints() -> ::garde::Constraints {
use ::garde::constraints::*;
Constraints::Struct(
Struct {
fields: vec![
(
"s".into(),
vec![
Rule::Length {
min: Some(1),
max: None
}
],
)
]
}
)
}
}
Thinking about this a bit more. Our support for arbitrary expr in certain rules throws a wrench into this being a fully compile-time thing.
If you have e.g. length(min=1), then that's a constant and we can embed it directly into the resulting serialized constraints. But given something like length(min=ctx.min), it's not clear how you'd serialize the ctx.min value if you don't have access to it without the user providing a context. Worse yet is length(min=self.min), which depends on the contents of self.
Hi, just using this crate for the first time today. I'm making an app which uses 'egui_i18n' and 'egui_form' with the 'garde' feature. Currently, when a validation error occurs the message is returned in English, however that's no good when you're making an app that needs to be in multiple languages.
egui_i18n uses the popular 'fluent-rs' ( crate for translations, when rendering error messages you need two things: 1 - the fluent translation key, 2 the arguments. in the case of form validation errors the arguments are the parameters on the garde derive constraints.
example struct:
struct MyForm {
#[garde(length(min = 1))]
name: String,
}
example translation file:
my-form-name = Name
my-form-name-error-length = Must be longer than { $min } characters.
And the $min fluent arg would come from the derive constraints.
However, there's another issue because 'max' is not specified in the example above, if it was you'd need both the $min and $max constraints and also use a different translation, as below:
example struct:
struct MyForm {
#[garde(length(min = 1))]
value1: String,
#[garde(length(max = 10))]
value2: String,
#[garde(length(min = 1, max = 10))]
value3: String,
}
example translation file:
form-field-error-length-too-short = Must be longer than { $min } characters.
form-field-error-length-too-long = Must be shorter than { $max } characters.
form-field-error-length-incorrect = Must be between { $min } and { $max } characters.
this presents an issue, how does the developer select the right translation key?
proposal - specification of the a key for each rule:
struct MyForm {
#[garde(length(min = 1, key = "form-field-error-length-too-short"))]
value1: String,
#[garde(length(max = 10, key = "form-field-error-length-too-long"))]
value2: String,
#[garde(length(min = 1, max = 10, key = "form-field-error-length-incorrect"))]
value3: String,
}
The key and the arguments could then be programmatically used to pick the right translation key and to supply the arguments to it.