typed-value 0.1.0

A type-safe, validatable value object.
Documentation
extern crate typed_value;

use typed_value::*;

const ALPHA_NUMERIC_REGEX: once_cell::sync::Lazy<regex::Regex> =
    once_cell::sync::Lazy::new(|| regex::Regex::new("^[A-Za-z0-9]*$").expect("invalid regex"));

enum AlphaNumericProperty {}

impl Property for AlphaNumericProperty {
    type Value = String;
    type Error = Box<dyn std::error::Error>;

    fn validate(value: &Self::Value) -> Result<(), Self::Error> {
        if ALPHA_NUMERIC_REGEX.is_match(value) {
            Ok(())
        } else {
            Err(anyhow::anyhow!("\"{}\" is not alpha_numeric.", value).into())
        }
    }
}

enum FixedLengthProperty<const N: usize> {}

impl<const N: usize> Property for FixedLengthProperty<N> {
    type Value = String;
    type Error = Box<dyn std::error::Error>;

    fn validate(value: &Self::Value) -> Result<(), Self::Error> {
        if value.chars().count() == N {
            Ok(())
        } else {
            Err(anyhow::anyhow!("\"{}\" length is not equal to {}.", value, N).into())
        }
    }
}

struct Compose2<V, A, B>(V, A, B);

impl<V, A, B> Property for Compose2<V, A, B>
where
    A: Property<Value = V, Error = Box<dyn std::error::Error>>,
    B: Property<Value = V, Error = Box<dyn std::error::Error>>,
{
    type Value = V;
    type Error = Vec<Box<dyn std::error::Error>>;

    fn validate(value: &V) -> Result<(), Self::Error> {
        match [A::validate, B::validate]
            .iter()
            .fold(Vec::new(), |mut acc, f| match f(value) {
                Ok(_) => acc,
                Err(e) => {
                    acc.push(e);
                    acc
                }
            }) {
            errors if errors.len() == 0 => Ok(()),
            errors => Err(errors),
        }
    }
}

type FixedLengthAlphaNumericProperty<const N: usize> =
    Compose2<String, FixedLengthProperty<N>, AlphaNumericProperty>;

type FixedLengthAlphaNumeric<const N: usize> = TypedValue<FixedLengthAlphaNumericProperty<N>>;

#[test]
fn ok_when_multiple_validation() {
    let value = "1234".to_owned();
    assert!(FixedLengthAlphaNumeric::<4>::new(value).is_ok());
}

#[test]
fn multiple_errors_invalid_alphanumeric_and_invalid_length() {
    let value = "1234☺️".to_owned();
    let errors = FixedLengthAlphaNumeric::<4>::new(value).unwrap_err();
    let actual = errors.len();
    let expected = 2;
    assert_eq!(actual, expected);
}