The Validate
trait
As aragog
is trying to be a complete ODM, being able to add validations to models is a useful feature.
Macro validations
Let's take the record example model:
#![allow(unused_variables)] fn main() { use aragog::{Record, Validate}; #[derive(Serialize, Deserialize, Clone, Record, Validate)] pub struct User { pub username: String, pub first_name: String, pub last_name: String, pub roles: Vec<String>, pub age: usize, } }
We added the Validate
trait derive, making this model perform validations before being written to the database.
Field validations
We can add quite some pre-made validation operations as following:
#![allow(unused_variables)] fn main() { use aragog::{Record, Validate}; #[derive(Serialize, Deserialize, Clone, Record, Validate)] pub struct User { // The *username* field must contain exactly 10 characters #[validate(length = 10)] pub username: String, // The *username* field must have at least 3 characters #[validate(min_length = 3)] pub first_name: String, // The *username* field must have its lenth between 3 and 30 #[validate(min_length = 5, max_length = 30)] pub last_name: String, // Each role must have an exact length of 5 #[validate_each(length = 5)] pub roles: Vec<String>, // The *age* field must be at least 18 #[validate(greater_or_equal(18))] pub age: usize, } }
When trying to create or save a User
document all validations must match
or a Error::ValidationError
will be returned with an explicit message.
The current available field attribute validation operation macros (can be chained):
-
String
or string slice fields:length(VAL)
validates the field has exact length of VALmin_length(VAL)
validates the field has a minimum length of VALmax_length(VAL)
validates the field has a maximum length of VALregex(REGEX)
validates the field matches the REGEX regular expression (which can be consts likeValidate::SIMPLE_EMAIL_REGEX
)
-
Numeric fields or ordered types
greater_than(VAL)
validated the field is greater than VALgreater_or_equal(VAL)
validated the field is greater or equal to VALlesser_than(VAL)
validated the field is lesser than VALlesser_or_equal(VAL)
validated the field is lesser or equal to VAL
-
Vector fields or ordered iterable types
max_count(VAL)
validates the field has a maximal number of elements of VALmin_count(VAL)
validates the field has a minimal number of elements of VALcount(VAL)
validates the field has a number of elements of VAL
Note: These operations use the
Iterator::count
method which might be expensive -
Other
is_some
validates theOption
field contains a valueis_none
validates theOption
field does not contain a valuefunc(FUNC)
calls the FUNC method (see Extra Validations)call_validations
calls the validations of the field allowing to propagate the validation calls. The field must be an type implementingValidate
Note: The macro doesn't guarantee the order of validations
Validate comparison between custom types
The following validation operations:
greater_than(VAL)
greater_or_equal(VAL)
lesser_than(VAL)
lesser_or_equal(VAL)
Can allow custom types implementing PartialOrd
Note: It also requires
Display
to generate the error, andCopy
for the borrow checker
So you can use a custom struct or enum like this:
#![allow(unused_variables)] fn main() { #[derive(Debug, Clone, Copy, PartialOrd, PartialEq)] enum CustomOrd { A, B, C, } #[derive(Validate)] struct Comparator { #[validate(lesser_than(CustomOrd::C), greater_than(CustomOrd::A))] pub field: CustomOrd, } }
Extra validations
On more complex cases, simple field validations are not enough and you may want to add custom validations.
use aragog::{Record, Validate};
#[derive(Serialize, Deserialize, Clone, Record, Validate)]
+ #[validate(func("custom_validations"))] // We added this global validation attribute on top of the struct
pub struct User {
+ #[validate(length = 10, func("validate_username"))] // We added this field validation attribute
pub username: String,
#[validate(min_length = 3)]
pub first_name: String,
#[validate(min_length = 5, max_length = 30)]
pub last_name: String,
#[validate(greater_or_equal(18))]
pub age: usize,
+ // These two fields require a more complex validation
+ pub phone: Option<String>,
+ pub phone_country_code: Option<String>,
}
+ impl User {
+ // We added the global custom validation method (uses multi-fields)
+ fn custom_validations(&self, errors: &mut Vec<String>) {
+ if self.phone.is_some() || self.phone_country_code.is_some() {
+ // We use built-in validation methods
+ Self::validate_field_presence("phone", &self.phone, errors);
+ Self::validate_field_presence("phone_country_code", &self.phone_country_code, erros);
+ }
+ }
+
+ // We added the field custom validation method (field-specific)
+ fn validate_username(field_name: &str, value: &str, errors: &mut Vec<String>) {
+ if value == "SUPERADMIN" {
+ // We can push our own validation errors
+ errors.push(format!("{} can't be SUPERADMIN", field_name))
+ }
+ }
+ }
The macro attribute #[validate(func("METHOD"))]"
must link to an existing method of your struct.
This method can follow various patterns:
- global validation method (top of the struct)
#![allow(unused_variables)] fn main() { fn my_method(&self, errors: &mut Vec<String>) {} }
- field validation method
#![allow(unused_variables)] fn main() { fn my_method(field_name: &str, field_value: &T, errors: &mut Vec<String>) {} }
T
being your field type
Note: The method can have any visibility and can return whatever you want
the errors
argument is a mutable array of error messages it contains all current errors and you can push your own errors in it.
When the validate()
method is called, this errors
vector is used to build the error message.