aragog 0.17.0

A simple lightweight object-document mapper for ArangoDB
Documentation
use regex::Regex;

use crate::Error;
use std::fmt::Display;
use std::slice;

/// The `Validate` trait of the Aragog library.
/// This trait provides the possibility to validate an instance or its fields formats or logic. Its main use
/// it to validate a new or updated [`Record`] model instance before saving it.
///
/// # Example
///
/// ```rust
/// # use aragog::{Record, Validate};
/// # use serde::{Deserialize, Serialize};
///
/// #[derive(Record, Clone, Deserialize, Serialize)]
/// pub struct User {
///     pub name: String,
///     pub age: u32,
///     pub email: String,
///     pub job: Option<String>,
///     pub phone: String,
/// }
///
/// impl Validate for User {
///     fn validations(&self, errors: &mut Vec<String>) {
///         if self.age > 18 {
///             Self::validate_field_presence("job", &self.job, errors);
///         }
///         Self::validate_min_len("name", &self.name, 6, errors);
///         Self::validate_len("phone", &self.phone, 10, errors);
///         Self::validate_numeric_string("phone", &self.phone, errors);
///         if self.age < 13 {
///             errors.push("You are too young to use this website".to_string());
///         }
///     }
/// }
/// ```
/// [`Record`]: crate::Record
pub trait Validate {
    /// Validates the object field formats, logic or anything.
    ///
    /// Calls the [`validations`] method and return `()` on success.
    ///
    /// # Errors
    ///
    /// Will render a complete [`Error`]::[`ValidationError`] on validation failure.
    ///
    /// [`validations`]: Self::validations
    /// [`Error`]: crate::Error
    /// [`ValidationError`]:crate::Error::ValidationError
    fn validate(&self) -> Result<(), Error> {
        let mut errors: Vec<String> = Vec::new();

        self.validations(&mut errors);

        if errors.is_empty() {
            Ok(())
        } else {
            let error_str = errors.join(", ");
            log::error!("{}", &error_str);
            Err(Error::ValidationError(error_str))
        }
    }

    /// Runs all the defined validation on fields and fills the `errors` string vector with custom error messages
    fn validations(&self, errors: &mut Vec<String>);

    /// Runs all validations and returns a `false` if they failed, on success `true` is returned
    #[must_use]
    fn is_valid(&self) -> bool {
        self.validate().is_ok()
    }

    /// Helper function to simply check the presence of a field. This function is usually used inside the
    /// [`validations`] method since it will fill the `errors` with a message if the `field` is missing.
    ///
    /// # Arguments
    ///
    /// * `field_name` - The string slice name of the field, will be used in the error message on failure
    /// * `field` - Optional value, if `field` is `Some<T>` the function will succeed
    /// * `errors` - the mutable reference of the error message vector like provided in [`validations`]
    ///
    /// # Returns
    ///
    /// `true` if `field` is `Some<T>` on failure, `false` is returned and `errors` stored a new message
    ///
    /// [`validations`]: Self::validations
    fn validate_field_presence<T>(
        field_name: &str,
        field: &Option<T>,
        errors: &mut Vec<String>,
    ) -> bool {
        match field {
            Some(_value) => true,
            None => {
                errors.push(format!("{} is missing", field_name));
                false
            }
        }
    }

    /// Helper function to simply check the absence of a field. This function is usually used inside the
    /// [`validations`] method since it will fill the `errors` with a message if the `field` is missing.
    ///
    /// # Arguments
    ///
    /// * `field_name` - The string slice name of the field, will be used in the error message on failure
    /// * `field` - Optional value, if `field` is `None` the function will succeed
    /// * `errors` - the mutable reference of the error message vector like provided in [`validations`]
    ///
    /// # Returns
    ///
    /// `true` if `field` is `None` on failure, `false` is returned and `errors` stored a new message
    ///
    /// [`validations`]: Self::validations
    fn validate_field_absence<T>(
        field_name: &str,
        field: &Option<T>,
        errors: &mut Vec<String>,
    ) -> bool {
        match field {
            Some(_value) => {
                errors.push(format!("{} should not be set", field_name));
                false
            }
            None => true,
        }
    }

    /// Validates that `str` is numeric. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `str` - the field value to validate
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_numeric_string(field_path: &str, str: &str, errors: &mut Vec<String>) -> bool {
        for char in str.chars() {
            if !char.is_ascii_digit() {
                errors.push(format!("{} '{}' is not numeric", field_path, str));
                return false;
            }
        }
        true
    }

    /// Validates that `str` is not longer than expected. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `str` - the field value to validate
    /// * `max_len` - The maximum length of `str`
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_max_len(
        field_path: &str,
        str: &str,
        max_len: usize,
        errors: &mut Vec<String>,
    ) -> bool {
        if str.len() > max_len {
            errors.push(format!(
                "{} '{}' is too long, max length: {}",
                field_path, str, max_len
            ));
            return false;
        }
        true
    }

    /// Validates that `str` is not shorter than expected. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `str` - the field value to validate
    /// * `min_len` - The minimum length of `str`
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_min_len(
        field_path: &str,
        str: &str,
        min_len: usize,
        errors: &mut Vec<String>,
    ) -> bool {
        if str.len() < min_len {
            errors.push(format!(
                "{} '{}' is too short, min length: {}",
                field_path, str, min_len
            ));
            return false;
        }
        true
    }

    /// Validates that `str` has the exact expected length. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `str` - the field value to validate
    /// * `len` - The expected length of `str`
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_len(field_path: &str, str: &str, len: usize, errors: &mut Vec<String>) -> bool {
        if str.len() != len {
            errors.push(format!(
                "{} '{}' has wrong length, please specify {} characters",
                field_path, str, len
            ));
            return false;
        }
        true
    }

    /// Validates the maximum number of elements in `field_value`.
    /// Usually used as a helper function for implementations of [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `field_value` - the [`Iter`] field value to validate
    /// * `max_count` - The maximum count of `field_value` elements
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    /// [`Iter`]: std::slice::Iter
    #[allow(dead_code)]
    fn validate_max_count<T>(
        field_path: &str,
        field_value: slice::Iter<T>,
        max_count: usize,
        errors: &mut Vec<String>,
    ) -> bool {
        if field_value.count() > max_count {
            errors.push(format!(
                "{} has too many elements, max count: {}",
                field_path, max_count
            ));
            return false;
        }
        true
    }

    /// Validates the minimum number of elements in `field_value`.
    /// Usually used as a helper function for implementations of [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `field_value` - the [`Iter`] field value to validate
    /// * `min_count` - The minimum count of `field_value` elements
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    /// [`Iter`]: std::slice::Iter
    #[allow(dead_code)]
    fn validate_min_count<T>(
        field_path: &str,
        field_value: slice::Iter<T>,
        min_count: usize,
        errors: &mut Vec<String>,
    ) -> bool {
        if field_value.count() < min_count {
            errors.push(format!(
                "{} doesn't have enough elements, min count: {}",
                field_path, min_count
            ));
            return false;
        }
        true
    }

    /// Validates the exact number of elements in `field_value`.
    /// Usually used as a helper function for implementations of [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `field_value` - the [`Iter`] field value to validate
    /// * `count` - The expected exact count of `field_value` elements
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    /// [`Iter`]: std::slice::Iter
    #[allow(dead_code)]
    fn validate_count<T>(
        field_path: &str,
        field_value: slice::Iter<T>,
        count: usize,
        errors: &mut Vec<String>,
    ) -> bool {
        if field_value.count() != count {
            errors.push(format!(
                "{} has a wrong number of elements, expected count: {}",
                field_path, count
            ));
            return false;
        }
        true
    }

    /// Validates that `str` matches a regexp. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `str` - the field value to validate
    /// * `regex` - The regular expression `str` must match
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_regex(field_path: &str, str: &str, regex: &str, errors: &mut Vec<String>) -> bool {
        let reg = match Regex::new(regex) {
            Ok(value) => value,
            Err(error) => {
                log::error!("FATAL: Wrong regex: {}", error);
                return false;
            }
        };
        let result = reg.is_match(str);
        if result {
            return true;
        }
        errors.push(format!("{} '{}' has incorrect format", field_path, str));
        false
    }

    /// Validates that `value` is greater than `min_value`. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `value` - the field value to validate
    /// * `min_value` - The comparison value
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_greater_than<T: PartialOrd + Display>(
        field_path: &str,
        value: T,
        min_value: T,
        errors: &mut Vec<String>,
    ) -> bool {
        if value <= min_value {
            errors.push(format!(
                "{} '{}' must be greater than {}",
                field_path, value, min_value
            ));
            return false;
        }
        true
    }

    /// Validates that `value` is greater or equal to `min_value`. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `value` - the field value to validate
    /// * `min_value` - The comparison value
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_greater_or_equal_to<T: PartialOrd + Display>(
        field_path: &str,
        value: T,
        min_value: T,
        errors: &mut Vec<String>,
    ) -> bool {
        if value < min_value {
            errors.push(format!(
                "{} '{}' must be greater or equal to {}",
                field_path, value, min_value
            ));
            return false;
        }
        true
    }

    /// Validates that `value` is lower than `max_value`. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `value` - the field value to validate
    /// * `max_value` - The comparison value
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_lesser_than<T: PartialOrd + Display>(
        field_path: &str,
        value: T,
        max_value: T,
        errors: &mut Vec<String>,
    ) -> bool {
        if value >= max_value {
            errors.push(format!(
                "{} '{}' must be lower than {}",
                field_path, value, max_value
            ));
            return false;
        }
        true
    }

    /// Validates that `value` is lower or equal to `max_value`. Usually used as a helper function for implementations of
    /// [`Validate`] trait.
    ///
    /// # Arguments
    ///
    /// * `field_path` - the string slice representing the field name or path for clear errors
    /// * `value` - the field value to validate
    /// * `max_value` - The comparison value
    /// * `errors` - a mutable reference to a vector of String to be filled with error messages like provided
    /// in [`Validate`]::[`validations`]
    ///
    /// # Returns
    ///
    /// On success `true` is returned and `errors` stays unchanged. On failure `false` is returned and a
    /// new error message is added to `errors`
    ///
    /// [`validations`]: Self::validations
    #[allow(dead_code)]
    fn validate_lesser_or_equal_to<T: PartialOrd + Display>(
        field_path: &str,
        value: T,
        max_value: T,
        errors: &mut Vec<String>,
    ) -> bool {
        if value > max_value {
            errors.push(format!(
                "{} '{}' must be lower or equal to {}",
                field_path, value, max_value
            ));
            return false;
        }
        true
    }

    /// A simple and fast regular expression to validate email formats
    const SIMPLE_EMAIL_REGEX: &'static str = r#"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}"#;

    /// RFC 5322 compliant regular expression to validate email formats from [emailregex.com](http://emailregex.com/).
    /// It is significantly slower than the `simple_email_regex`
    const RFC_5322_EMAIL_REGEX: &'static str = r#"(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"#;
}

#[cfg(test)]
mod tests {
    use super::*;

    const STRING_EMPTY: &str = "";

    struct TestElem;

    impl Validate for TestElem {
        fn validations(&self, _errors: &mut Vec<String>) {}
    }

    mod string_validators {
        use super::*;

        mod numeric_string {
            use super::*;

            #[test]
            fn validates_only_numeric_characters() {
                let mut errors = Vec::new();
                let correct_str = "0123456789";
                let wrong_strs = ["+33122334", "01 02 03", "1.23"];

                assert!(TestElem::validate_numeric_string(
                    STRING_EMPTY,
                    correct_str,
                    &mut errors
                ));
                assert!(errors.is_empty());
                for wrong_str in wrong_strs {
                    assert!(!TestElem::validate_numeric_string(
                        STRING_EMPTY,
                        wrong_str,
                        &mut errors
                    ));
                    assert!(!errors.is_empty());
                    errors.pop().unwrap();
                    assert!(errors.is_empty());
                }
            }
        }

        mod max_length {
            use super::*;

            #[test]
            fn validates_only_correct_strings() {
                let mut errors = Vec::new();
                let max = 10;
                let correct_strs = ["hello", "foo1", "bar-++", "0102030405"];
                let wrong_strs = [
                    "hello678911",
                    "foobarfoobar1",
                    "bar-+123lkdzacdeee+",
                    "010203040005",
                ];

                for correct_str in correct_strs {
                    assert!(TestElem::validate_max_len(
                        STRING_EMPTY,
                        correct_str,
                        max,
                        &mut errors,
                    ));
                    assert!(errors.is_empty());
                }
                for wrong_str in wrong_strs {
                    assert!(!TestElem::validate_max_len(
                        STRING_EMPTY,
                        wrong_str,
                        max,
                        &mut errors,
                    ));
                    assert!(!errors.is_empty());
                    errors.pop().unwrap();
                    assert!(errors.is_empty());
                }
            }
        }

        mod min_length {
            use super::*;

            #[test]
            fn validates_only_correct_strings() {
                let mut errors = Vec::new();
                let min = 10;
                let correct_strs = [
                    "hello678911",
                    "foobarfoobar1",
                    "bar-+123lkdzacdeee+",
                    "010203040005",
                ];
                let wrong_strs = ["hello", "foo1", "bar-++", "010203040"];

                for correct_str in correct_strs {
                    assert!(TestElem::validate_min_len(
                        STRING_EMPTY,
                        correct_str,
                        min,
                        &mut errors,
                    ));
                    assert!(errors.is_empty());
                }
                for wrong_str in wrong_strs {
                    assert!(!TestElem::validate_min_len(
                        STRING_EMPTY,
                        wrong_str,
                        min,
                        &mut errors,
                    ));
                    assert!(!errors.is_empty());
                    errors.pop().unwrap();
                    assert!(errors.is_empty());
                }
            }
        }

        mod exact_length {
            use super::*;

            #[test]
            fn validates_only_correct_strings() {
                let mut errors = Vec::new();
                let length = 10;
                let correct_strs = ["0102030405", "a-bct09a=(", "felix faur"];
                let wrong_strs = ["hello", "foo1barbarbar", "bar-++", "01020304051123"];

                for correct_str in correct_strs {
                    assert!(TestElem::validate_len(
                        STRING_EMPTY,
                        correct_str,
                        length,
                        &mut errors,
                    ));
                    assert!(errors.is_empty());
                }
                for wrong_str in wrong_strs {
                    assert!(!TestElem::validate_len(
                        STRING_EMPTY,
                        wrong_str,
                        length,
                        &mut errors,
                    ));
                    assert!(!errors.is_empty());
                    errors.pop().unwrap();
                    assert!(errors.is_empty());
                }
            }
        }

        mod regex {
            use super::*;

            const REGEX: &str = "^[a-z]{1,10}$";

            #[test]
            fn validate_regex_works() {
                let mut errors = Vec::new();
                let valid_strs = ["abc", "hellothere", "felix"];
                let wrong_strs = ["abc1", "hellotherebro", "felix de_manevi11e", "a bc"];

                for valid_str in valid_strs {
                    assert!(TestElem::validate_regex(
                        STRING_EMPTY,
                        valid_str,
                        REGEX,
                        &mut errors
                    ));
                    assert!(errors.is_empty());
                }
                for wrong_str in wrong_strs {
                    assert!(!TestElem::validate_regex(
                        STRING_EMPTY,
                        wrong_str,
                        REGEX,
                        &mut errors
                    ));
                    assert!(!errors.is_empty());
                    errors.pop();
                    assert!(errors.is_empty());
                }
            }
        }
    }
}