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
710 downloads per month
Used in valust
81KB
2K
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
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)]
- Syntax:
- 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")]
- Syntax:
Validator Expression
Transformer Expression
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
Forward
ing 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 rename
d 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