From 27ccba525202d8ca850a0485e9c790ab833b34e3 Mon Sep 17 00:00:00 2001 From: fef Date: Fri, 20 Jan 2023 20:19:41 +0100 Subject: [PATCH] util: add ValidateField trait --- src/util/validate.rs | 48 ++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/util/validate.rs b/src/util/validate.rs index 8760fc1..72436cf 100644 --- a/src/util/validate.rs +++ b/src/util/validate.rs @@ -10,11 +10,17 @@ pub struct Sane(T); #[repr(transparent)] pub struct Insane(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(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, + N: Into<&'a str>, F: FnOnce(FieldResultBuilder) -> FieldResultBuilder, { 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(self, success_val: T) -> Result { - 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 {