8 releases (breaking)

0.8.0 Jan 24, 2025
0.7.0 Jan 24, 2025
0.6.0 Jan 21, 2025
0.5.0 Jan 16, 2025
0.1.0 Jan 10, 2025

#67 in #validator

Download history 277/week @ 2025-01-06 222/week @ 2025-01-13 333/week @ 2025-01-20 19/week @ 2025-01-27 13/week @ 2025-02-03

710 downloads per month
Used in valust

MIT/Apache

81KB
2K SLoC

Valust-Derive

Crate VersionGitHub top language Crates.io Downloads (recent)


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

Generic Attributes

These attributes can be used on either fields or the structure itself.

forward_attr

Syntax forward_attr(<attribute>)
Description Add external attribute to the raw data type
Example #[forward_attr(serde(rename_all="camelCase"))]

Field Attributes

valid

Syntax valid(<valid exprs>)
Description Add a validator to check a field.
Example #[valid(expr(a > 10))]

Reference: valid expr

trans

Syntax trans(<trans exprs>)
Description Add a transformer to modify a field.
Example #[trans(expr(try(a.parse::<u32>())))]

Reference: trans expr

forward

Syntax forward
Description Forward the field.
Example #[forward]

Structure Attributes

rename

Syntax rename(<ident>) or rename = "<ident>"(not recommended)
Description Rename the raw data structure.
Example #[rename(RawData)] or #[rename = "RawData"]

forward_derive

Syntax forward_derive(<derive-items>)
Description Add derive attribute to the raw data structure.
Example #[forward_derive(Debug, Clone)]

pre

Syntax pre(<struct-valid-expr>)
Description Add struct-level validator before all field validators.
Example #[pre((magic1 + magic2 == 10, "invalid magic number"))]

Reference: struct-valid-expr

post

Syntax post(<struct-valid-expr>)
Description Add struct-level validator after all field validators.
Example #[post((magic1 + magic2 == 10, "invalid magic number"))]

Reference: struct-valid-expr

Special Expressions

Struct-level Validator Expression

  • Plain validator:
    • Syntax: <expr>
    • Example: a > 10
    • Full attr: #[pre(a > 10)]
  • Validator with custom message:
    • Syntax: (<expr>, <msg>)
    • Example: (b != 0, "`b` must be non-zero")
    • Full attr: #[pre(b != 0, "`b` must be non-zero")]

Validator Expression

Reference

Transformer Expression

Reference

Example

use valust::Validate;
use valust_derive::Valust;
use valust_utils::convert::parse_to;
    
#[derive(Debug, Valust)]
#[forward_derive(Debug)]
pub struct Inner {
    #[valid(expr(code > 10.0, "code must be greater than 10.0"))]
    pub code: f64,
}

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

Macro Expansion

#[automatically_derived]
#[derive(Debug)]
pub struct RawInner {
    pub code: f64,
}
#[automatically_derived]
#[allow(
    non_camel_case_types,
    non_snake_case,
    unused_variables,
    non_upper_case_globals
)]
impl ::valust::Validate for Inner {
    type Raw = RawInner;
    fn validate(raw: Self::Raw) -> Result<Self, ::valust::error::ValidationError> {
        let RawInner { code } = raw;
        let mut valust_impl_err_Inner = ::valust::error::ValidationError::new();
        valust_impl_err_Inner.check()?;
        let mut valust_impl_err_Inner = ::valust::error::ValidationError::new();
        fn valust_validate_code(
            code: f64,
            valust_err_code: &mut ::valust::error::ValidationError,
        ) -> Option<f64> {
            if !({ code > 10.0 }) {
                valust_err_code.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",
                    },
                );
                return None;
            }
            Some(code)
        }
        let code: Option<f64> = valust_validate_code(code, &mut valust_impl_err_Inner);
        valust_impl_err_Inner.check()?;
        let code = code.expect("Unexpected error occurred in processing field `code`");
        let mut valust_impl_err_Inner = ::valust::error::ValidationError::new();
        valust_impl_err_Inner.check()?;
        Ok(Inner { code })
    }
}

#[automatically_derived]
#[derive(Debug)]
pub struct RawOuter {
    pub inner: ::valust::Raw<Inner>,
    pub extra: String,
}
#[automatically_derived]
#[allow(
    non_camel_case_types,
    non_snake_case,
    unused_variables,
    non_upper_case_globals
)]
impl ::valust::Validate for Outer {
    type Raw = RawOuter;
    fn validate(raw: Self::Raw) -> Result<Self, ::valust::error::ValidationError> {
        let RawOuter { inner, extra } = raw;
        let mut valust_impl_err_Outer = ::valust::error::ValidationError::new();
        valust_impl_err_Outer.check()?;
        let mut valust_impl_err_Outer = ::valust::error::ValidationError::new();
        fn valust_validate_inner(
            inner: ::valust::Raw<Inner>,
            valust_err_inner: &mut ::valust::error::ValidationError,
        ) -> Option<Inner> {
            let inner: Inner = match ::valust::Validate::validate(inner) {
                Ok(v_valust) => v_valust,
                Err(e_valust) => {
                    valust_err_inner.extend_error("inner", e_valust);
                    return None;
                }
            };
            Some(inner)
        }
        let inner: Option<Inner> =
            valust_validate_inner(inner, &mut valust_impl_err_Outer);
        fn valust_validate_extra(
            extra: String,
            valust_err_extra: &mut ::valust::error::ValidationError,
        ) -> Option<u32> {
            let extra = ({ extra.trim() });
            let extra = {
                let valust_format_err_clone_extra = extra.clone();
                match ((parse_to::<u32>)(extra)) {
                    ::std::result::Result::Ok(valust_v) => valust_v,
                    ::std::result::Result::Err(valust_trans_err_cause) => {
                        valust_err_extra.push_transform_error(
                            ::valust::error::transform::TransformError {
                                field: "extra",
                                path: format!("{}", "extra"),
                                value: format!(
                                    "(String) {:?}",
                                    valust_format_err_clone_extra
                                ),
                                cause: ::std::boxed::Box::new(valust_trans_err_cause),
                                message: ::std::option::Option::Some(
                                    "`extra`'s transform expression fails",
                                ),
                                expression: "(parse_to :: < u32 >) (extra)",
                                source_type_name: "String",
                                target_type_name: "<unknown>",
                            },
                        );
                        return None;
                    }
                }
            };
            Some(extra)
        }
        let extra: Option<u32> =
            valust_validate_extra(extra, &mut valust_impl_err_Outer);
        valust_impl_err_Outer.check()?;
        let inner =
            inner.expect("Unexpected error occurred in processing field `inner`");
        let extra =
            extra.expect("Unexpected error occurred in processing field `extra`");
        let mut valust_impl_err_Outer = ::valust::error::ValidationError::new();
        valust_impl_err_Outer.check()?;
        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.

The raw data type could be inferred by the compiler, so you don't need to specify it even if you've renamed it.

Regex validator

valust-derive supports regex-based validator expressions using regex. To enable regex support, you must enable the regex feature for both valust and valust-derive.

Note: valust > regex feature will enable valust-derive > regex if derive feature is enabled.

Why we need rename

Though we don't need to specify the raw data type when executing the validator, we might need to construct them directly. Sadly, due to rust's language syntax limitations, we cannot construct an unnamed struct using type alias (i.e. valust::Raw::<Foo>). That's why we may need rename a raw type.

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

~210–650KB
~15K SLoC