validator Rust Library

repository·master·Indexed 25 days ago

https://github.com/keats/validator

A Rust macro-based validation library inspired by Marshmallow and Django. It provides a declarative way to define validation rules on structs using the `Validate` derive macro and `#[validate(...)]` attributes. It includes built-in validators for email, URL, length, range, and regex, as well as support for custom validation functions, nested struct validation, and struct-level schema validation.

Tokens
6.7K
Snippets
10
Records
36
Agent score
81%

What's inside validator

  1. Understand ValidationErrorsKind and error structures

    master

    When validation fails, validate() returns ValidationErrors. The errors are categorized by the ValidationErrorsKind enum, which determines how errors are nested:

    • Struct(Box<ValidationErrors>): Used for errors found in a single nested struct (via #[validate(nested)]).
    • List(BTreeMap<usize, Box<ValidationErrors>>): Used for errors found in a vector of nested structs, keyed by the index of the invalid entry.
    • Field(Vec<ValidationError>): Used for simple field-level errors.

    A ValidationError contains:

    • code: A Cow<'static, str> representing the error type.
    • message: An optional Cow<'static, str> describing the error.
    • params: A HashMap<Cow<'static, str>, Value> containing metadata, including the field's value automatically.
    #[derive(Debug, Serialize, Clone, PartialEq)]
    #[serde(untagged)]
    pub enum ValidationErrorsKind {
        Struct(Box<ValidationErrors>),
        List(BTreeMap<usize, Box<ValidationErrors>>),
        Field(Vec<ValidationError>),
    }
    
    #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
    pub struct ValidationError {
      pub code: Cow<'static, str>,
      pub message: Option<Cow<'static, str>>,
      pub params: HashMap<Cow<'static, str>, Value>,
    }
  2. Perform struct-level validation with schema

    master

    If validation requires access to the entire struct (e.g., comparing two fields), use the #[validate(schema(function = "..."))] attribute at the struct level. This function is called after all individual field validations are complete.

    • skip_on_field_errors: Defaults to true. If true, the schema function is only called if all field-level validations pass. If false, it runs regardless.
    • Errors from the schema function are keyed under __all__ in the error map.
    #[derive(Debug, Validate, Deserialize)]
    #[validate(schema(function = "validate_category", skip_on_field_errors = false))]
    struct CategoryData {
        category: String,
        name: String,
    }
  3. Implement custom validation functions

    master

    You can define custom validation logic in two ways:

    1. Simple Custom Function

    Pass a function name to #[validate(custom(function = "..."))]. The function must accept the field value and return Result<(), ValidationError>.

    2. Custom Validation with Context (Arguments)

    To pass external data (like a database connection) to your validator, use the context and use_context attributes.

    1. Define a context struct.
    2. Use #[validate(context = Type)] on the struct being validated.
    3. Use #[validate(custom(function = "...", use_context))] on the field.
    4. Call .validate_with_args(&context) instead of .validate().

    Note: Custom validation with arguments does not work on nested validation.

    use validator::{Validate, ValidateArgs, ValidationError};
    
    fn validate(value: &str, context: &TestContext) -> Result<(), ValidationError> {
        // ... logic
        Ok(())
    }
    
    struct TestContext(i64, i64);
    
    #[derive(Debug, Validate)]
    #[validate(context = TestContext)]
    struct TestStruct {
        #[validate(custom(function = "validate", use_context))]
        value: String,
    }
    
    let test_struct = TestStruct { value: "..." };
    let test_context = TestContext(1, 2);
    test_struct.validate_with_args(&test_context).is_ok();
  4. Use the Validate derive macro for struct validation

    master

    The Validate derive macro allows you to define validation rules directly on struct fields using #[validate(...)] attributes. To perform validation, call the .validate() method on your struct instance, which returns a Result<(), ValidationErrors>.

    use serde::Deserialize;
    use validator::{Validate, ValidationError};
    
    #[derive(Debug, Validate, Deserialize)]
    struct SignupData {
        #[validate(email)]
        mail: String,
        #[validate(url)]
        site: String,
        #[validate(length(min = 1), custom(function = "validate_unique_username"))]
        #[serde(rename = "firstName")]
        first_name: String,
        #[validate(range(min = 18, max = 20))]
        age: u32,
        #[validate(range(exclusive_min = 0.0, max = 100.0))]
        height: f32,
    }
    
    fn validate_unique_username(username: &str) -> Result<(), ValidationError> {
        if username == "xXxShad0wxXx" {
            return Err(ValidationError::new("terrible_username"));
        }
        Ok(())
    }
    
    // Usage:
    match signup_data.validate() {
      Ok(_) => (),
      Err(e) => return e,
    };
  5. Perform validation on nested structs and vectors

    master

    To validate nested data structures, ensure the child types also implement Validate (via #[derive(Validate)]) and use the #[validate(nested)] attribute on the parent struct's fields.

    use serde::Deserialize;
    use validator::Validate;
    
    #[derive(Debug, Validate, Deserialize)]
    struct SignupData {
        #[validate(nested)]
        contact_details: ContactDetails,
        #[validate(nested)]
        preferences: Vec<Preference>,
        #[validate(required)]
        allow_cookies: Option<bool>,
    }
    
    #[derive(Debug, Validate, Deserialize)]
    struct ContactDetails {
        #[validate(email)]
        mail: String,
    }
    
    #[derive(Debug, Validate, Deserialize)]
    struct Preference {
        #[validate(length(min = 4))]
        name: String,
        value: bool,
    }
    
    // Usage:
    match signup_data.validate() {
      Ok(_) => (),
      Err(e) => return e,
    };
  6. Represent complex validation results with ValidationErrors

    master
    The ValidationErrors struct is a container for one or more validation failures. It uses a HashMap to map field names to a ValidationErrorsKind, which determines how the errors are structured (as a single field's errors, a nested struct, or a list of errors from a collection).
  7. Available crate features

    master

    The validator crate provides the following features:

    • derive: Enables the use of the derive macro for easy validation implementation.
    • derive_nightly_features: Imports both derive and proc-macro-error2 nightly features. This allows proc-macro-error2 to emit extra nightly warnings during compilation.
  8. Supported types for `ValidateLength`

    master

    The ValidateLength<u64> trait is implemented for a wide variety of standard Rust types. The method of length calculation depends on the type:

    Character-based length (Unicode characters)

    Used for types where length is defined by the number of characters:

    • str and &str
    • String

    Element-based length (Number of elements)

    Used for collections where length is defined by the number of items/elements:

    • Vec<T>
    • [T; N] (Arrays)
    • VecDeque<T>
    • BTreeSet<T>
    • BTreeMap<K, V>
    • HashSet<T, S>
    • HashMap<K, V, S>
    • IndexSet<T> (with indexmap feature)
    • IndexMap<K, V> (with indexmap feature)
    • Slices [T]

    Wrapper types

    • Option<T>: If Some(val), it validates the length of val. If None, it returns true (as length is None).
    • Cow<'_, T>: Delegates to the underlying type.
    • Smart Pointers: Arc<T>, Box<T>, Rc<T>, Ref<'_, T>, and RefMut<'_, T> all delegate to the inner type T.
  9. How validation works for collections

    master

    The validator crate provides automatic Validate implementations for several standard collection types. When you validate a collection, the validator iterates through its elements and attempts to validate each one. If any element fails, the errors are aggregated into a ValidationErrorsKind::List.

    Supported collections include:

    • Vec<T>
    • [T; N] (Arrays)
    • HashSet<T>
    • BTreeSet<T>
    • BinaryHeap<T>
    • LinkedList<T>
    • VecDeque<T>
    • &HashMap<K, V, S> (Validates the values)
    • &BTreeMap<K, V> (Validates the values)

    For maps, only the values are validated; the keys are not checked by the collection's Validate implementation.

  10. Override error codes for validators

    master

    You can override the default error code for any validator using the code argument within the #[validate] attribute. This is often used to provide unique identifiers for error handling logic, especially for custom validators.

    Supported syntax examples:

    #[validate(email(code = "code_str"))]
    #[validate(credit_card(code = "code_str"))]
    #[validate(length(min = 5, max = 10, code = "code_str"))]
    #[validate(regex(path = *static_regex, code = "code_str"))]
    #[validate(custom(function = "custom_fn", code = "code_str"))]
    #[validate(contains(pattern = "pattern_str", code = "code_str"))]
    #[validate(does_not_contain(pattern = "pattern_str", code = "code_str"))]
    #[validate(must_match(other = "match_value", code = "code_str"))]
  11. Customize error messages and codes in validators

    master

    Every validator in keats/validator accepts two optional arguments to customize error reporting:

    • message: A custom string to accompany the error. This is useful for internationalization (i18n) or providing user-friendly feedback.
    • code: A custom error code string. While every validator has a default code (e.g., the regex validator defaults to regex), you can override it to suit your application's error handling logic, which is particularly useful when using the custom validator.

    Note: These arguments (message and code) cannot be applied to nested validation calls triggered via the #[validate] attribute.

    // Example: Using both message and code
    #[validate(url(message = "message", code = "code_str"))]
    #[validate(email(code = "code_str", message = "message"))]
    #[validate(custom(function = "custom_fn", code = "code_str", message = "message_str"))]