typed_value/
lib.rs

1#![no_std]
2#![deny(missing_docs)]
3
4//! A type-safe, validatable value object.
5
6#[cfg(test)]
7#[macro_use]
8extern crate std;
9
10/// Trait that represents the property of the value.
11pub trait Property {
12    /// Value type.
13    type Value;
14
15    /// Validation error type.
16    type Error;
17
18    /// Method for validating value.
19    fn validate(value: &Self::Value) -> Result<(), Self::Error>;
20}
21
22/// Value object with constraints by Property trait.
23pub struct TypedValue<P: Property> {
24    inner: P::Value,
25}
26
27impl<P: Property> TypedValue<P> {
28    /// Method to instantiate a TypedValue.
29    ///
30    /// # Examples
31    ///
32    /// ```
33    /// use typed_value::*;
34    ///
35    /// struct MaximumLengthProperty<T, const N:usize>(T);
36    ///
37    /// impl<T: AsRef<str>, const N:usize> Property for MaximumLengthProperty<T, N> {
38    ///     type Value = T;
39    ///     type Error = String;
40    ///
41    ///     fn validate(value: &Self::Value) -> Result<(), Self::Error> {
42    ///         if value.as_ref().chars().count() <= N {
43    ///             Ok(())
44    ///         } else {
45    ///             Err(format!("length of \"{}\" is over {}.", value.as_ref(), N))
46    ///         }
47    ///     }
48    /// }
49    ///
50    /// type MaximumLengthString<const N:usize> = TypedValue<MaximumLengthProperty<String, N>>;
51    ///
52    /// assert!(MaximumLengthString::<5>::new("foobar".to_owned()).is_err());
53    /// ```
54    pub fn new(value: P::Value) -> Result<Self, P::Error> {
55        P::validate(&value)?;
56
57        Ok(TypedValue { inner: value })
58    }
59}
60
61impl<P: Property<Value = V>, V: PartialEq> PartialEq for TypedValue<P> {
62    fn eq(&self, other: &Self) -> bool {
63        V::eq(&self.inner, &other.inner)
64    }
65}
66
67impl<P: Property<Value = V>, V: Eq> Eq for TypedValue<P> {}
68
69impl<P: Property<Value = V>, V: PartialOrd> PartialOrd for TypedValue<P> {
70    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
71        V::partial_cmp(&self.inner, &other.inner)
72    }
73
74    fn lt(&self, other: &Self) -> bool {
75        V::lt(&self.inner, &other.inner)
76    }
77
78    fn le(&self, other: &Self) -> bool {
79        V::le(&self.inner, &other.inner)
80    }
81
82    fn gt(&self, other: &Self) -> bool {
83        V::gt(&self.inner, &other.inner)
84    }
85
86    fn ge(&self, other: &Self) -> bool {
87        V::ge(&self.inner, &other.inner)
88    }
89}
90
91impl<P: Property<Value = V>, V: Ord> Ord for TypedValue<P> {
92    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
93        V::cmp(&self.inner, &other.inner)
94    }
95}
96
97impl<P: Property<Value = V>, V: core::hash::Hash> core::hash::Hash for TypedValue<P> {
98    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
99        V::hash(&self.inner, state);
100    }
101}
102
103impl<P: Property<Value = V>, V: Clone> Clone for TypedValue<P> {
104    fn clone(&self) -> Self {
105        Self {
106            inner: V::clone(&self.inner),
107        }
108    }
109}
110
111impl<P: Property<Value = V>, V: Copy> Copy for TypedValue<P> {}
112
113impl<P: Property<Value = V>, V: core::fmt::Debug> core::fmt::Debug for TypedValue<P> {
114    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115        V::fmt(&self.inner, f)
116    }
117}
118
119impl<P: Property<Value = V>, V: core::fmt::Display> core::fmt::Display for TypedValue<P> {
120    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121        V::fmt(&self.inner, f)
122    }
123}
124
125impl<P: Property> core::ops::Deref for TypedValue<P> {
126    type Target = P::Value;
127
128    fn deref(&self) -> &Self::Target {
129        &self.inner
130    }
131}
132
133/// Trait that provides a wrapping method to TypedValue for any value.
134pub trait TypedValueExt: Sized {
135    /// # Examples
136    ///
137    /// ```
138    /// use typed_value::*;
139    ///
140    /// struct MaximumLengthProperty<T, const N:usize>(T);
141    ///
142    /// impl<T: AsRef<str>, const N:usize> Property for MaximumLengthProperty<T, N> {
143    ///     type Value = T;
144    ///     type Error = String;
145    ///
146    ///     fn validate(value: &Self::Value) -> Result<(), Self::Error> {
147    ///         if value.as_ref().chars().count() <= N {
148    ///             Ok(())
149    ///         } else {
150    ///             Err(format!("length of \"{}\" is over {}.", value.as_ref(), N))
151    ///         }
152    ///     }
153    /// }
154    ///
155    /// type MaximumLengthString<const N:usize> = TypedValue<MaximumLengthProperty<String, N>>;
156    ///
157    /// let foobar = "foobar".to_owned();
158    /// let _: MaximumLengthString<6> = foobar.typed().unwrap();
159    /// ```
160    fn typed<P: Property<Value = Self>>(self) -> Result<TypedValue<P>, P::Error>;
161}
162
163impl<T> TypedValueExt for T {
164    fn typed<P: Property<Value = Self>>(self) -> Result<TypedValue<P>, P::Error> {
165        TypedValue::new(self)
166    }
167}
168
169#[cfg(test)]
170mod property_based_tests {
171    use super::*;
172    use quickcheck_macros::quickcheck;
173
174    struct Stub<T>(T);
175
176    impl<T> Property for Stub<T> {
177        type Value = T;
178        type Error = ();
179
180        fn validate(_: &Self::Value) -> Result<(), Self::Error> {
181            Ok(())
182        }
183    }
184
185    #[quickcheck]
186    fn equivalent_when_wrapped_and_then_unwrapped(value: u8) {
187        assert_eq!(*TypedValue::<Stub<_>>::new(value).unwrap(), value)
188    }
189}