Skip to main content

g_type/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4use core::{
5    borrow::Borrow,
6    cmp::PartialOrd,
7    convert::Infallible,
8    error::Error,
9    fmt,
10    hash::{Hash, Hasher},
11    marker::PhantomData,
12};
13
14pub mod aliases;
15
16#[cfg(feature = "serde")]
17mod serde;
18
19/// Validation strategy for [`GType`].
20///
21/// A validator can:
22///
23/// - Define an optional minimum value via [`Validator::min`].
24/// - Define an optional maximum value via [`Validator::max`].
25/// - Perform arbitrary validation via [`Validator::validate`].
26///
27/// Validation is performed when constructing a [`GType`] using
28/// [`GType::try_new`].
29///
30/// # Note
31/// [`Validator::Target`] is comparison target for checking bounds for T.
32/// Its value must be comparable to itself and T.
33/// Putting non-comparable value(e.g [`f32::NAN`]) might give error construction.
34///
35/// # Example
36///
37/// ```rust
38/// use core::convert::Infallible;
39/// use g_type::{GType, Validator};
40///
41/// struct Percent;
42///
43/// impl Validator<u8> for Percent {
44///     type Target = u8;
45///     type Error = Infallible;
46///
47///     fn max() -> Option<&'static Self::Target> {
48///         Some(&100)
49///     }
50/// }
51///
52/// let value = GType::<u8, Percent>::try_new(42);
53/// assert!(value.is_ok());
54/// ```
55pub trait Validator<T> {
56    /// Target type for bounds comparison.
57    ///
58    /// # Examples
59    /// - u32 -> u32
60    /// - String -> str
61    /// - Vec\<T\> -> \[T\] or \[T; N\]
62    type Target: PartialOrd<Self::Target> + PartialOrd<T> + ?Sized + 'static;
63
64    /// Validation error type.
65    type Error;
66
67    /// Minimum value in range, inclusive.
68    #[inline]
69    fn min() -> Option<&'static Self::Target> {
70        None
71    }
72
73    /// Maximum value in range, inclusive.
74    #[inline]
75    fn max() -> Option<&'static Self::Target> {
76        None
77    }
78
79    /// Validation logics.
80    #[inline]
81    fn validate(_: &T) -> Result<(), Self::Error> {
82        Ok(())
83    }
84}
85
86/// Validator that performs no validation.
87///
88/// This is the default validator used by [`GType`].
89///
90/// ```rust
91/// use g_type::GType;
92///
93/// let value: GType<_> = GType::try_new(123).unwrap();
94/// ```
95#[derive(Debug, Clone, Copy)]
96pub struct NoValidation;
97
98impl<T: PartialOrd + 'static> Validator<T> for NoValidation {
99    type Target = T;
100    type Error = Infallible;
101}
102
103impl<T: PartialOrd + 'static> Validator<T> for () {
104    type Target = T;
105    type Error = Infallible;
106}
107
108/// Error returned when constructing a [`GType`].
109#[non_exhaustive]
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub enum GTypeError<E> {
112    /// When minimum value exceeds maximum value.
113    MinExceedsMax,
114
115    /// The value is below the validator's minimum bound.
116    BelowMinimum,
117
118    /// The value is above the validator's maximum bound.
119    AboveMaximum,
120
121    /// The validator rejected the value.
122    Validation(E),
123}
124
125impl<E: fmt::Display> fmt::Display for GTypeError<E> {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            Self::MinExceedsMax => f.write_str("minimum value exceeds maximum value"),
129            Self::BelowMinimum => f.write_str("value is below minimum"),
130            Self::AboveMaximum => f.write_str("value is above maximum"),
131            Self::Validation(err) => write!(f, "validation error: {}", err),
132        }
133    }
134}
135
136impl<E: Error + 'static> Error for GTypeError<E> {
137    fn source(&self) -> Option<&(dyn Error + 'static)> {
138        match self {
139            Self::Validation(err) => Some(err),
140            Self::MinExceedsMax | Self::BelowMinimum | Self::AboveMaximum => None,
141        }
142    }
143}
144
145/// A validated value.
146///
147/// `GType<T, V>` wraps a value of type `T` and guarantees that it
148/// satisfied validator `V` at construction time.
149///
150/// Validation is performed by [`GType::try_new`]. Once constructed,
151/// the contained value cannot be modified directly, preserving the
152/// validator's invariants.
153///
154/// # Examples
155///
156/// ```rust
157/// use g_type::GType;
158///
159/// let value = GType::<u32>::try_new(42).unwrap();
160///
161/// assert_eq!(*value.as_ref(), 42);
162/// ```
163///
164/// With a custom validator:
165///
166/// ```rust
167/// use core::convert::Infallible;
168/// use g_type::{GType, Validator};
169///
170/// struct Percent;
171///
172/// impl Validator<u8> for Percent {
173///     type Target = u8;
174///     type Error = Infallible;
175///
176///     fn max() -> Option<&'static Self::Target> {
177///         Some(&100)
178///     }
179/// }
180///
181/// assert!(GType::<u8, Percent>::try_new(50).is_ok());
182/// assert!(GType::<u8, Percent>::try_new(150).is_err());
183/// ```
184#[repr(transparent)]
185pub struct GType<T, V = NoValidation> {
186    value: T,
187    _marker: PhantomData<V>,
188}
189
190impl<T: PartialOrd<V::Target>, V: Validator<T>> GType<T, V> {
191    #[inline]
192    pub(crate) const fn new(value: T) -> Self {
193        Self {
194            value,
195            _marker: PhantomData,
196        }
197    }
198
199    /// Attempts to create a validated value.
200    ///
201    /// Returns an error if the value violates the validator's
202    /// minimum bound, maximum bound, or custom validation rules.
203    pub fn try_new(value: T) -> Result<Self, GTypeError<V::Error>> {
204        #[allow(clippy::neg_cmp_op_on_partial_ord)]
205        match (V::min(), V::max()) {
206            (Some(min), Some(max)) if !(min <= max) => {
207                return Err(GTypeError::MinExceedsMax);
208            }
209            (Some(min), _) if !(&value >= min) => {
210                return Err(GTypeError::BelowMinimum);
211            }
212            (_, Some(max)) if !(&value <= max) => {
213                return Err(GTypeError::AboveMaximum);
214            }
215            _ => (),
216        }
217
218        V::validate(&value).map_err(GTypeError::Validation)?;
219
220        Ok(Self::new(value))
221    }
222
223    /// Returns a shared reference to the underlying value.
224    #[inline]
225    pub const fn as_ref(&self) -> &T {
226        &self.value
227    }
228
229    /// Consumes the wrapper and returns the underlying value.
230    #[inline]
231    pub fn into_inner(self) -> T {
232        self.value
233    }
234
235    /// Calls a function with a reference to the contained value.
236    ///
237    /// Returns `self` unchanged.
238    ///
239    /// This is primarily useful for debugging and logging in method
240    /// chains.
241    #[inline]
242    pub fn inspect<F>(self, func: F) -> Self
243    where
244        F: FnOnce(&T),
245    {
246        func(&self.value);
247        self
248    }
249
250    /// Transforms the contained value into another validated type.
251    ///
252    /// The transformed value is validated using the destination
253    /// validator before being returned.
254    ///
255    /// This behaves similarly to `Option::map` and `Result::map`.
256    #[inline]
257    pub fn map<U, UV, F>(self, func: F) -> Result<GType<U, UV>, GTypeError<UV::Error>>
258    where
259        U: PartialOrd<UV::Target>,
260        UV: Validator<U>,
261        F: FnOnce(T) -> U,
262    {
263        GType::<U, UV>::try_new(func(self.value))
264    }
265
266    /// Chains another fallible validated transformation.
267    ///
268    /// This behaves similarly to `Option::and_then` and
269    /// `Result::and_then`.
270    #[inline]
271    pub fn and_then<U, UV, F>(self, func: F) -> Result<GType<U, UV>, GTypeError<UV::Error>>
272    where
273        U: PartialOrd<UV::Target>,
274        UV: Validator<U>,
275        F: FnOnce(T) -> Result<GType<U, UV>, GTypeError<UV::Error>>,
276    {
277        func(self.value)
278    }
279}
280
281impl<T, V> AsRef<T> for GType<T, V> {
282    #[inline]
283    fn as_ref(&self) -> &T {
284        &self.value
285    }
286}
287
288impl<T, V> Borrow<T> for GType<T, V> {
289    #[inline]
290    fn borrow(&self) -> &T {
291        &self.value
292    }
293}
294
295impl<T, V> Clone for GType<T, V>
296where
297    T: Clone,
298{
299    #[inline]
300    fn clone(&self) -> Self {
301        Self {
302            value: self.value.clone(),
303            _marker: PhantomData,
304        }
305    }
306}
307
308impl<T, V> Copy for GType<T, V> where T: Copy {}
309
310impl<T, V> fmt::Debug for GType<T, V>
311where
312    T: fmt::Debug,
313{
314    #[inline]
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        self.value.fmt(f)
317    }
318}
319
320impl<T, V> fmt::Display for GType<T, V>
321where
322    T: fmt::Display,
323{
324    #[inline]
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        self.value.fmt(f)
327    }
328}
329
330impl<T, U, LHSV, RHSV> PartialEq<GType<U, RHSV>> for GType<T, LHSV>
331where
332    T: PartialEq<U>,
333    U: PartialEq<T>,
334{
335    #[inline]
336    fn eq(&self, other: &GType<U, RHSV>) -> bool {
337        self.value == other.value
338    }
339}
340
341impl<T, V> Eq for GType<T, V> where T: Eq {}
342
343impl<T, U, LHSV, RHSV> PartialOrd<GType<U, RHSV>> for GType<T, LHSV>
344where
345    T: PartialOrd<U>,
346    U: PartialOrd<T>,
347{
348    #[inline]
349    fn partial_cmp(&self, other: &GType<U, RHSV>) -> Option<core::cmp::Ordering> {
350        self.value.partial_cmp(&other.value)
351    }
352}
353
354impl<T, V> Ord for GType<T, V>
355where
356    T: Ord,
357{
358    #[inline]
359    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
360        self.value.cmp(&other.value)
361    }
362}
363
364impl<T, V> Hash for GType<T, V>
365where
366    T: Hash,
367{
368    #[inline]
369    fn hash<H: Hasher>(&self, state: &mut H) {
370        self.value.hash(state);
371    }
372}