util: add ValidateField trait

This commit is contained in:
anna 2023-01-20 20:19:41 +01:00
parent 4be3bdda0c
commit 27ccba5252
Signed by: fef
GPG key ID: EC22E476DC2D3D84

View file

@ -10,11 +10,17 @@ pub struct Sane<T>(T);
#[repr(transparent)] #[repr(transparent)]
pub struct Insane<T>(T); pub struct Insane<T>(T);
/// Anything that can be validated (used for transforming [Insane] to [Sane]) /// Anything that can be validated (used for transforming [`Insane`] to [`Sane`])
pub trait Validate: Sized { pub trait Validate: Sized {
fn validate(&self, builder: ResultBuilder) -> ResultBuilder; fn validate(&self, builder: ResultBuilder) -> ResultBuilder;
} }
/// A field in a struct that can be validated and that is reused in multiple places.
/// Primarily useful for newtype structs.
pub trait ValidateField<'a>: Sized {
fn validate_field(builder: FieldResultBuilder<'a, Self>) -> FieldResultBuilder<'a, Self>;
}
/// Validation error /// Validation error
#[derive(Debug)] #[derive(Debug)]
pub struct Error { pub struct Error {
@ -82,34 +88,42 @@ impl ResultBuilder {
} }
/// Begin sanitization on a single field /// Begin sanitization on a single field
pub fn field<T, N, F>(mut self, val: &T, name: N, check: F) -> Self pub fn field<'a, T, N, F>(mut self, val: &'a T, name: N, check: F) -> Self
where where
N: Into<String>, N: Into<&'a str>,
F: FnOnce(FieldResultBuilder<T>) -> FieldResultBuilder<T>, F: FnOnce(FieldResultBuilder<T>) -> FieldResultBuilder<T>,
{ {
let builder = check(FieldResultBuilder { let builder = check(FieldResultBuilder {
val, val,
errors: Vec::with_capacity(0), errors: Vec::with_capacity(0),
}); });
let name = name.into(); builder.append_errors(name.into(), &mut self.errors);
self.errors.extend( self
builder }
.errors
.into_iter() /// Validate a field that implements [`ValidateField`]
.map(|msg| (name.clone(), msg.clone())), pub fn field_auto<'a, T, N>(mut self, val: &'a T, name: N) -> Self
); where
T: ValidateField<'a>,
N: Into<&'a str>,
{
let builder = T::validate_field(FieldResultBuilder {
val,
errors: Vec::with_capacity(0),
});
builder.append_errors(name.into(), &mut self.errors);
self self
} }
/// Obtain the final result of the validation /// Obtain the final result of the validation
fn result<T>(self, success_val: T) -> Result<T> { fn result<T>(self, success_val: T) -> Result<T> {
if self.errors.len() > 0 { if self.errors.is_empty() {
Ok(success_val)
} else {
Err(Error { Err(Error {
errors: self.errors, errors: self.errors,
} }
.into()) .into())
} else {
Ok(success_val)
} }
} }
} }
@ -126,6 +140,14 @@ impl<'a, T> FieldResultBuilder<'a, T> {
} }
self self
} }
fn append_errors(self, field_name: &str, errors: &mut Vec<(String, String)>) {
errors.extend(
self.errors
.into_iter()
.map(|msg| (String::from(field_name), msg)),
);
}
} }
impl fmt::Display for Error { impl fmt::Display for Error {