use std::fmt;
use std::marker::PhantomData;
use super::Predicate;
#[derive(Clone, Copy, Default)]
pub struct And<A, B>(PhantomData<(A, B)>);
impl<A, B> fmt::Debug for And<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"And<{}, {}>",
std::any::type_name::<A>(),
std::any::type_name::<B>()
)
}
}
impl<T, A, B> Predicate<T> for And<A, B>
where
A: Predicate<T>,
B: Predicate<T>,
{
type Error = AndError<A::Error, B::Error>;
fn check(value: &T) -> Result<(), Self::Error> {
let a_result = A::check(value);
let b_result = B::check(value);
match (a_result, b_result) {
(Ok(()), Ok(())) => Ok(()),
(Err(a), Ok(())) => Err(AndError::First(a)),
(Ok(()), Err(b)) => Err(AndError::Second(b)),
(Err(a), Err(b)) => Err(AndError::Both(a, b)),
}
}
fn description() -> &'static str {
"both predicates must hold"
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AndError<A, B> {
First(A),
Second(B),
Both(A, B),
}
impl<A: fmt::Display, B: fmt::Display> fmt::Display for AndError<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AndError::First(a) => write!(f, "{}", a),
AndError::Second(b) => write!(f, "{}", b),
AndError::Both(a, b) => write!(f, "{}; {}", a, b),
}
}
}
impl<A: std::error::Error + 'static, B: std::error::Error + 'static> std::error::Error
for AndError<A, B>
{
}
unsafe impl<A: Send, B: Send> Send for AndError<A, B> {}
unsafe impl<A: Sync, B: Sync> Sync for AndError<A, B> {}
#[derive(Clone, Copy, Default)]
pub struct Or<A, B>(PhantomData<(A, B)>);
impl<A, B> fmt::Debug for Or<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Or<{}, {}>",
std::any::type_name::<A>(),
std::any::type_name::<B>()
)
}
}
impl<T, A, B> Predicate<T> for Or<A, B>
where
A: Predicate<T>,
B: Predicate<T>,
{
type Error = OrError<A::Error, B::Error>;
fn check(value: &T) -> Result<(), Self::Error> {
match A::check(value) {
Ok(()) => Ok(()),
Err(a_err) => match B::check(value) {
Ok(()) => Ok(()),
Err(b_err) => Err(OrError(a_err, b_err)),
},
}
}
fn description() -> &'static str {
"at least one predicate must hold"
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OrError<A, B>(pub A, pub B);
impl<A: fmt::Display, B: fmt::Display> fmt::Display for OrError<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "neither predicate held: {} and {}", self.0, self.1)
}
}
impl<A: std::error::Error + 'static, B: std::error::Error + 'static> std::error::Error
for OrError<A, B>
{
}
unsafe impl<A: Send, B: Send> Send for OrError<A, B> {}
unsafe impl<A: Sync, B: Sync> Sync for OrError<A, B> {}
#[derive(Clone, Copy, Default)]
pub struct Not<A>(PhantomData<A>);
impl<A> fmt::Debug for Not<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Not<{}>", std::any::type_name::<A>())
}
}
impl<T, A: Predicate<T>> Predicate<T> for Not<A> {
type Error = NotError;
fn check(value: &T) -> Result<(), Self::Error> {
match A::check(value) {
Ok(()) => Err(NotError(A::description())),
Err(_) => Ok(()),
}
}
fn description() -> &'static str {
"predicate must not hold"
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NotError(pub &'static str);
impl fmt::Display for NotError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "value must NOT satisfy: {}", self.0)
}
}
impl std::error::Error for NotError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::refined::predicates::numeric::{Negative, NonZero, Positive};
use crate::refined::predicates::string::{NonEmpty, Trimmed};
use crate::refined::Refined;
#[test]
fn test_and_both_pass() {
type CleanString = Refined<String, And<NonEmpty, Trimmed>>;
let result = CleanString::new("hello".to_string());
assert!(result.is_ok());
}
#[test]
fn test_and_first_fails() {
type CleanString = Refined<String, And<NonEmpty, Trimmed>>;
let result = CleanString::new("".to_string());
assert!(result.is_err());
match result.unwrap_err() {
AndError::First(_) | AndError::Both(_, _) => (),
_ => panic!("Expected First or Both error"),
}
}
#[test]
fn test_and_second_fails() {
type CleanString = Refined<String, And<NonEmpty, Trimmed>>;
let result = CleanString::new(" hello ".to_string());
assert!(result.is_err());
match result.unwrap_err() {
AndError::Second(_) => (),
_ => panic!("Expected Second error"),
}
}
#[test]
fn test_and_both_fail() {
type BothFail = Refined<i32, And<Positive, Negative>>;
let result = BothFail::new(0);
assert!(result.is_err());
match result.unwrap_err() {
AndError::Both(_, _) => (),
_ => panic!("Expected Both error"),
}
}
#[test]
fn test_or_first_passes() {
type NonZeroAlt = Refined<i32, Or<Positive, Negative>>;
let result = NonZeroAlt::new(5);
assert!(result.is_ok());
}
#[test]
fn test_or_second_passes() {
type NonZeroAlt = Refined<i32, Or<Positive, Negative>>;
let result = NonZeroAlt::new(-5);
assert!(result.is_ok());
}
#[test]
fn test_or_both_fail() {
type NonZeroAlt = Refined<i32, Or<Positive, Negative>>;
let result = NonZeroAlt::new(0);
assert!(result.is_err());
}
#[test]
fn test_not_inverts() {
type NotPositive = Refined<i32, Not<Positive>>;
assert!(NotPositive::new(0).is_ok());
assert!(NotPositive::new(-5).is_ok());
assert!(NotPositive::new(5).is_err());
}
#[test]
fn test_not_error_message() {
type NotPositive = Refined<i32, Not<Positive>>;
let result = NotPositive::new(5);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.0.contains("positive"));
}
#[test]
fn test_complex_composition() {
type Complex = Refined<i32, And<Positive, Or<NonZero, Negative>>>;
assert!(Complex::new(5).is_ok());
assert!(Complex::new(0).is_err());
assert!(Complex::new(-5).is_err());
}
#[test]
fn test_and_error_display() {
let err: AndError<&str, &str> = AndError::First("first error");
assert_eq!(format!("{}", err), "first error");
let err: AndError<&str, &str> = AndError::Second("second error");
assert_eq!(format!("{}", err), "second error");
let err: AndError<&str, &str> = AndError::Both("first", "second");
assert_eq!(format!("{}", err), "first; second");
}
#[test]
fn test_or_error_display() {
let err = OrError("first", "second");
assert_eq!(
format!("{}", err),
"neither predicate held: first and second"
);
}
#[test]
fn test_not_error_display() {
let err = NotError("positive number (> 0)");
assert_eq!(
format!("{}", err),
"value must NOT satisfy: positive number (> 0)"
);
}
}