use std::ops::Deref;
pub trait Validation: Sized {
type Error;
fn is_valid(&self) -> Result<(), Self::Error>;
fn try_validate(self) -> Result<Valid<Self>, Self::Error> {
self.is_valid()?;
Ok(Valid(self))
}
}
pub trait CompositeValidation: Validation {
fn is_valid_composite(&self) -> Result<(), <Self as Validation>::Error>;
}
pub trait OptionsOf<C>: CompositeValidation {
fn set_options(&mut self, value: C);
}
impl<C> OptionsOf<C> for C
where
C: CompositeValidation,
{
fn set_options(&mut self, value: C) {
*self = value;
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Valid<T: Validation>(T);
impl<T: Validation> Deref for Valid<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Validation> Valid<T> {
pub fn into_value(self) -> T {
self.0
}
pub fn set(self, set_fn: impl FnOnce(&mut T)) -> Result<Self, <T as Validation>::Error> {
let mut inner = self.0;
set_fn(&mut inner);
inner.try_validate()
}
pub fn with<F>(self, options: F) -> Result<Self, <T as Validation>::Error>
where
T: OptionsOf<F>,
F: Validation<Error = <T as Validation>::Error>,
{
let mut inner = self.0;
inner.set_options(options.try_validate()?.0);
inner.is_valid_composite()?;
Ok(Self(inner))
}
pub fn with2<F1, F2>(self, options1: F1, options2: F2) -> Result<Self, <T as Validation>::Error>
where
T: OptionsOf<F1> + OptionsOf<F2>,
F1: Validation<Error = <T as Validation>::Error>,
F2: Validation<Error = <T as Validation>::Error>,
{
let mut inner = self.0;
inner.set_options(options1.try_validate()?.0);
inner.set_options(options2.try_validate()?.0);
inner.is_valid_composite()?;
Ok(Self(inner))
}
pub fn with3<F1, F2, F3>(
self,
options1: F1,
options2: F2,
options3: F3,
) -> Result<Self, <T as Validation>::Error>
where
T: OptionsOf<F1> + OptionsOf<F2> + OptionsOf<F3>,
F1: Validation<Error = <T as Validation>::Error>,
F2: Validation<Error = <T as Validation>::Error>,
F3: Validation<Error = <T as Validation>::Error>,
{
let mut inner = self.0;
inner.set_options(options1.try_validate()?.0);
inner.set_options(options2.try_validate()?.0);
inner.set_options(options3.try_validate()?.0);
inner.is_valid_composite()?;
Ok(Self(inner))
}
}
#[cfg(test)]
mod test {
use super::*;
#[derive(Debug, Default, Clone, Eq, PartialEq)]
struct Test {
pub field: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
struct Test2 {
pub field: u32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
struct Test3 {
pub field: u32,
}
#[derive(Debug, Clone, Eq, PartialEq)]
enum TestError {
Test(Test),
Test2(Test2),
Test3(Test3),
TestParent(TestParent),
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
struct TestParent {
pub child: Test,
pub child2: Test2,
pub child3: Test3,
}
impl OptionsOf<Test> for TestParent {
fn set_options(&mut self, value: Test) {
self.child = value;
}
}
impl OptionsOf<Test2> for TestParent {
fn set_options(&mut self, value: Test2) {
self.child2 = value;
}
}
impl OptionsOf<Test3> for TestParent {
fn set_options(&mut self, value: Test3) {
self.child3 = value;
}
}
impl Validation for Test {
type Error = TestError;
fn is_valid(&self) -> Result<(), Self::Error> {
if self.field > 10 {
Ok(())
} else {
Err(TestError::Test(self.clone()))
}
}
}
impl Validation for Test2 {
type Error = TestError;
fn is_valid(&self) -> Result<(), Self::Error> {
if self.field > 20 {
Ok(())
} else {
Err(TestError::Test2(self.clone()))
}
}
}
impl Validation for Test3 {
type Error = TestError;
fn is_valid(&self) -> Result<(), Self::Error> {
if self.field > 20 {
Ok(())
} else {
Err(TestError::Test3(self.clone()))
}
}
}
impl CompositeValidation for TestParent {
fn is_valid_composite(&self) -> Result<(), Self::Error> {
if self.child.field == self.child2.field {
Ok(())
} else {
Err(TestError::TestParent(self.clone()))
}
}
}
impl Validation for TestParent {
type Error = TestError;
fn is_valid(&self) -> Result<(), Self::Error> {
self.child.is_valid()?;
self.child2.is_valid()?;
self.child3.is_valid()?;
self.is_valid_composite()
}
}
struct TestBuilder(Valid<TestParent>);
impl TestBuilder {
pub fn new() -> Self {
Self(Default::default())
}
pub fn with2<F1, F2>(
mut self,
options1: F1,
options2: F2,
) -> Result<Self, <TestParent as Validation>::Error>
where
TestParent: OptionsOf<F1> + OptionsOf<F2>,
F1: Validation<Error = <TestParent as Validation>::Error>,
F2: Validation<Error = <TestParent as Validation>::Error>,
{
self.0 = self.0.with2(options1, options2)?;
Ok(self)
}
pub fn build(self) -> Valid<TestParent> {
self.0
}
}
#[test]
fn test_validation() {
let test = Test { field: 40 };
let test2 = Test2 { field: 40 };
let test3 = Test3 { field: 40 };
let parent: Valid<TestParent> = TestParent {
child: Test { field: 30 },
child2: Test2 { field: 30 },
child3: Test3 { field: 30 },
}
.try_validate()
.unwrap()
.set(|p| p.child3.field = 120)
.unwrap();
let parent = parent.with2(test.clone(), test2.clone()).unwrap();
let parent = parent.with3(test.clone(), test2, test3).unwrap();
assert_eq!(
parent.child.field, test.field,
"{} != {}",
parent.child.field, test.field
);
let fail = parent.with(Test { field: 1234 });
assert_eq!(
fail,
Err(TestError::TestParent(TestParent {
child: Test { field: 1234 },
child2: Test2 { field: 40 },
child3: Test3 { field: 40 }
}))
);
let parent = TestBuilder::new()
.with2(Test { field: 100 }, Test2 { field: 100 })
.unwrap()
.build();
assert_ne!(
parent.child.field, test.field,
"{} == {}",
parent.child.field, test.field
);
}
}