3 releases (breaking)

new 0.3.1 Jan 12, 2025
0.3.0 Jan 12, 2025
0.2.0 Jan 11, 2025
0.1.0 Jan 10, 2025

#68 in #validator

Download history 212/week @ 2025-01-05

215 downloads per month
Used in valust

MIT/Apache

58KB
1.5K SLoC

Valust-Derive

Output

The derive macro Valust will emit both an extra structure containing raw data and an implementation of valust::Validator.

The raw data struct is identical to the structure definition given to the Valust macro (i.e., struct A { a: A } becomes struct RawA { a: A }, and struct B(B) becomes struct RawB(B)). The fields are automatically derived by Valust. Specifically, fields with only valid retain their type, fields with one or more trans have the type of the first transformer in the first trans, and fields with forward are determined by the forward field.

The default naming pattern of the raw data struct is RawXXX. To override it, use the rename struct attribute.

Syntax

Field Attributes

Name Syntax Description Example
valid valid(<validator>) Validator, uses raw data as input. valid(a > 10)
trans trans(<transformer>) Transformer, converts raw data to transformed data. trans(try(String => s.parse::<u32>()))
forward forward, forward(Ident) Forward the field and uses it's validator to validate the field. itself. forward(RawInput)
display display = bool, display(bool) Whether to display the value when an error occurs. Enabled by default.
Performance issues.
display = false

Structure Attributes

Name Syntax Description Example
forward_derive forward_derive(Ident) Add derive macros for the raw data struct. forward_derive(Debug)
pre pre(<validator>) Pre-validator, which uses raw data as input. pre(a > 10, b + c != 0.0)
post post(<validator>) Post-validator, which uses validated data as input. post(a > 10)
rename rename = "Ident", rename(Ident) Rename the output raw data struct. rename(Original)

Special Expressions

Validator Expression

  • Plain validator:
    • Syntax: valid(<expr>)
    • Example: valid(a > 10)
  • Validator with custom message:
    • Syntax: valid((<expr>, <msg>))
    • Example: valid((b != 0, "`b` must be non-zero"))
  • Fallible validator:
    • Syntax: valid(try(<expr>))
    • Example: valid(try(s.parse::<u32>() > 10))
  • Fallible validator with message:
    • Syntax: valid(try(<expr>, <msg>))
    • Example: valid(try(s.parse::<u32>() > 10, "`s` must be non-zero-string))

Transformer Expression

  • Plain transformer:
    • Syntax: trans(Type => <expr>)
    • Example: trans(String => s.trim())
  • Fallible transformer:
    • Syntax: trans(try(Type => <expr>))
    • Example: trans(try(String => s.parse::<u32>()))
  • Fallible transformer with message:
    • Syntax: trans(try(Type => <expr>, <msg>))
    • Example: trans(try(String => s.parse::<u32>(), "fail to parse number"))

Example

use valust::Validate;
use valust_utils::convert::parse_to;
    
#[derive(Debug, Valust)]
#[forward_derive(Debug)]
pub struct Inner {
    #[display = true]
    #[valid(code > 10.0)]
    pub code: f64,
}

#[derive(Debug, Valust)]
#[forward_derive(Debug)]
pub struct Outer {
    #[forward]
    pub inner: Inner,
    #[trans(String => extra.trim())]
    #[trans(try(String => fn(parse_to::<u32>)))]
    pub extra: u32,
}

Macro Expansion

#[automatically_derived]
#[derive(Debug)]
pub struct RawInner {
    pub code: f64,
}
#[automatically_derived]
impl ::valust::Validate<Inner> for RawInner {
    fn validate(
        self,
    ) -> ::std::result::Result<Inner, ::valust::error::ValidationError> {
        #![allow(non_snake_case)]
        let RawInner { code } = self;
        let mut __valust_error_Inner = ::valust::error::ValidationError::new();
        fn _valust_process_code(
            code: f64,
            _valust_error: &mut ::valust::error::ValidationError,
        ) -> ::std::option::Option<f64> {
            if !(code > 10.0) {
                _valust_error.push_validate_error(
                    ::valust::error::validate::ValidateError {
                        field: "code",
                        path: format!("{}", "code"),
                        value: format!("(f64) {:?}", code),
                        cause: ::std::option::Option::None,
                        message: ::std::option::Option::Some(
                            "code must be greater than 10.0",
                        ),
                        expression: "code > 10.0",
                        type_name: "f64",
                    },
                );
            }
            ::std::option::Option::Some(code)
        }
        let code: ::std::option::Option<f64> =
            _valust_process_code(code, &mut __valust_error_Inner);
        __valust_error_Inner.check()?;
        let mut __valust_error_Inner = ::valust::error::ValidationError::new();
        let code =
            code.expect("Unexpected error occurred while processing field `code`");
        __valust_error_Inner.check()?;
        ::std::result::Result::Ok(Inner { code })
    }
}

#[automatically_derived]
#[derive(Debug)]
pub struct RawOuter {
    pub inner: RawInner,
    pub extra: String,
}
#[automatically_derived]
impl ::valust::Validate<Outer> for RawOuter {
    fn validate(
        self,
    ) -> ::std::result::Result<Outer, ::valust::error::ValidationError> {
        #![allow(non_snake_case)]
        let RawOuter { inner, extra } = self;
        let mut __valust_error_Outer = ::valust::error::ValidationError::new();
        fn _valust_process_inner(
            inner: RawInner,
            _valust_error: &mut ::valust::error::ValidationError,
        ) -> ::std::option::Option<Inner> {
            let inner = match (<RawInner as ::valust::Validate<Inner>>::validate(inner))
            {
                Ok(value) => value,
                Err(__valust_err_inner) => {
                    _valust_error.extend_error("inner", __valust_err_inner);
                    return None;
                }
            };
            ::std::option::Option::Some(inner)
        }
        let inner: ::std::option::Option<Inner> =
            _valust_process_inner(inner, &mut __valust_error_Outer);
        fn _valust_process_extra(
            extra: String,
            _valust_error: &mut ::valust::error::ValidationError,
        ) -> ::std::option::Option<u32> {
            let extra = extra.trim();
            let __format_err_clone_extra = extra.clone();
            let extra = match (parse_to::<u32>(extra)) {
                Ok(value) => value,
                Err(__valust_err_extra) => {
                    _valust_error.push_transform_error(
                        ::valust::error::transform::TransformError {
                            field: "extra",
                            path: format!("{}", "extra"),
                            value: format!("(String) {:?}", __format_err_clone_extra),
                            cause: ::std::boxed::Box::new(__valust_err_extra),
                            message: ::std::option::Option::None,
                            expression: "parse_to :: < u32 > (extra)",
                            source_type_name: "String",
                            target_type_name: "u32",
                        },
                    );
                    return None;
                }
            };
            ::std::option::Option::Some(extra)
        }
        let extra: ::std::option::Option<u32> =
            _valust_process_extra(extra, &mut __valust_error_Outer);
        __valust_error_Outer.check()?;
        let mut __valust_error_Outer = ::valust::error::ValidationError::new();
        let inner =
            inner.expect("Unexpected error occurred while processing field `inner`");
        let extra =
            extra.expect("Unexpected error occurred while processing field `extra`");
        __valust_error_Outer.check()?;
        ::std::result::Result::Ok(Outer { inner, extra })
    }
}

Appendix

Forwarding a field

Fields that implement valust::Validator are not automatically recognized by Valust, but Valust can leverage pre-defined Validator implementations. Specifically, by using the forward field attribute, Valust will execute the valust::Validator::validate method of the field's type, automatically extending the error path field. Additionally, it will automatically change the corresponding field of the raw data struct to the original data type of that field.

If no parameter is provided to forward, it will automatically use the default naming pattern (i.e., RawXXX).

Performance issues when displaying error messages

Displaying huge data may lead to performance issues, as the internal formatter will clone the data for fear that user-defined expressions might take the field by-value instead of by-ref.

Dependencies

~255–710KB
~17K SLoC