use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use core::ops::Deref;
use crate::Validator;
#[repr(transparent)]
pub struct Refined<T, V: Validator<T>> {
value: T,
_validator: PhantomData<fn() -> V>,
}
impl<T, V: Validator<T>> Refined<T, V> {
pub fn new(value: T) -> Result<Self, V::Error> {
V::validate(&value)?;
Ok(Self {
value,
_validator: PhantomData,
})
}
#[must_use]
pub fn get(&self) -> &T {
&self.value
}
#[must_use]
pub fn into_inner(self) -> T {
self.value
}
}
impl<T, V: Validator<T>> Deref for Refined<T, V> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T, V: Validator<T>> AsRef<T> for Refined<T, V> {
fn as_ref(&self) -> &T {
&self.value
}
}
impl<T: Clone, V: Validator<T>> Clone for Refined<T, V> {
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
_validator: PhantomData,
}
}
}
impl<T: Copy, V: Validator<T>> Copy for Refined<T, V> {}
impl<T: fmt::Debug, V: Validator<T>> fmt::Debug for Refined<T, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Refined").field(&self.value).finish()
}
}
impl<T: fmt::Display, V: Validator<T>> fmt::Display for Refined<T, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.value.fmt(f)
}
}
impl<T: PartialEq, V: Validator<T>> PartialEq for Refined<T, V> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T: Eq, V: Validator<T>> Eq for Refined<T, V> {}
impl<T: PartialOrd, V: Validator<T>> PartialOrd for Refined<T, V> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.value.partial_cmp(&other.value)
}
}
impl<T: Ord, V: Validator<T>> Ord for Refined<T, V> {
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}
impl<T: Hash, V: Validator<T>> Hash for Refined<T, V> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use crate::ValidationError;
struct NonEmpty;
impl Validator<&str> for NonEmpty {
type Error = ValidationError;
fn validate(value: &&str) -> Result<(), Self::Error> {
if value.is_empty() {
Err(ValidationError::new("non_empty", "value must not be empty"))
} else {
Ok(())
}
}
}
struct Positive;
impl Validator<i32> for Positive {
type Error = ValidationError;
fn validate(value: &i32) -> Result<(), Self::Error> {
if *value > 0 {
Ok(())
} else {
Err(ValidationError::new("positive", "value must be > 0"))
}
}
}
#[derive(Clone, PartialEq, Debug)]
struct Tag(i32);
struct AnyTag;
impl Validator<Tag> for AnyTag {
type Error = ValidationError;
fn validate(_value: &Tag) -> Result<(), Self::Error> {
Ok(())
}
}
#[test]
fn new_accepts_valid_and_rejects_invalid() {
assert!(Refined::<&str, NonEmpty>::new("ok").is_ok());
let err = Refined::<&str, NonEmpty>::new("").unwrap_err();
assert_eq!(err.code(), "non_empty");
}
#[test]
fn accessors_return_inner() {
let n = Refined::<i32, Positive>::new(42).unwrap();
assert_eq!(*n.get(), 42);
assert_eq!(*n.as_ref(), 42);
assert_eq!(*n, 42); assert_eq!(n.into_inner(), 42);
}
#[test]
fn delegated_traits_compare_by_value() {
let a = Refined::<i32, Positive>::new(1).unwrap();
let b = Refined::<i32, Positive>::new(1).unwrap();
let c = Refined::<i32, Positive>::new(2).unwrap();
assert_eq!(a, b);
assert!(a < c);
}
#[test]
fn copy_inner_makes_refined_copy() {
let a = Refined::<i32, Positive>::new(3).unwrap();
let b = a; assert_eq!(*a.get(), 3);
assert_eq!(*b.get(), 3);
}
#[test]
fn clone_inner_clones_refined_without_revalidating() {
let a = Refined::<Tag, AnyTag>::new(Tag(7)).unwrap();
let b = a.clone();
assert_eq!(*a.get(), Tag(7));
assert_eq!(*b.get(), Tag(7));
}
#[test]
fn layout_is_transparent_over_t() {
assert_eq!(
core::mem::size_of::<Refined<i32, Positive>>(),
core::mem::size_of::<i32>(),
);
assert_eq!(
core::mem::size_of::<Refined<&str, NonEmpty>>(),
core::mem::size_of::<&str>(),
);
}
}