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)]
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 {
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
#[derive(Debug)]
pub struct Error {
@ -82,34 +88,42 @@ impl ResultBuilder {
}
/// 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
N: Into<String>,
N: Into<&'a str>,
F: FnOnce(FieldResultBuilder<T>) -> FieldResultBuilder<T>,
{
let builder = check(FieldResultBuilder {
val,
errors: Vec::with_capacity(0),
});
let name = name.into();
self.errors.extend(
builder
.errors
.into_iter()
.map(|msg| (name.clone(), msg.clone())),
);
builder.append_errors(name.into(), &mut self.errors);
self
}
/// Validate a field that implements [`ValidateField`]
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
}
/// Obtain the final result of the validation
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 {
errors: self.errors,
}
.into())
} else {
Ok(success_val)
}
}
}
@ -126,6 +140,14 @@ impl<'a, T> FieldResultBuilder<'a, T> {
}
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 {