use core::marker::PhantomData;
pub trait Praedicatum<T>: Sized {
fn check(value: &T) -> bool;
fn description() -> &'static str;
fn error_message() -> &'static str {
Self::description()
}
fn name() -> &'static str {
core::any::type_name::<Self>()
}
}
pub trait Predicate<T>: Praedicatum<T> {}
impl<T, P: Praedicatum<T>> Predicate<T> for P {}
pub trait Validatus<P> {
fn is_valid(&self) -> bool;
fn validate(&self) -> Result<(), &'static str>;
}
impl<T, P: Praedicatum<T>> Validatus<P> for T {
#[inline]
fn is_valid(&self) -> bool {
P::check(self)
}
#[inline]
fn validate(&self) -> Result<(), &'static str> {
if P::check(self) {
Ok(())
} else {
Err(P::error_message())
}
}
}
pub struct EvidentiaPredicati<T, P: Praedicatum<T>> {
_marker: PhantomData<(T, P)>,
}
impl<T, P: Praedicatum<T>> EvidentiaPredicati<T, P> {
#[inline]
pub fn verify(value: &T) -> Option<Self> {
if P::check(value) {
Some(EvidentiaPredicati {
_marker: PhantomData,
})
} else {
None
}
}
#[inline]
pub unsafe fn assume() -> Self {
EvidentiaPredicati {
_marker: PhantomData,
}
}
}
impl<T, P: Praedicatum<T>> Clone for EvidentiaPredicati<T, P> {
fn clone(&self) -> Self {
*self
}
}
impl<T, P: Praedicatum<T>> Copy for EvidentiaPredicati<T, P> {}
impl<T, P: Praedicatum<T>> core::fmt::Debug for EvidentiaPredicati<T, P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "EvidentiaPredicati<{}>", P::name())
}
}
#[derive(Debug, Clone)]
pub enum ExitusPraedicati {
Verum,
Falsum {
description: &'static str,
},
}
impl ExitusPraedicati {
#[inline]
pub fn passed(&self) -> bool {
matches!(self, ExitusPraedicati::Verum)
}
#[inline]
pub fn failed(&self) -> bool {
matches!(self, ExitusPraedicati::Falsum { .. })
}
#[inline]
pub fn to_result(self) -> Result<(), &'static str> {
match self {
ExitusPraedicati::Verum => Ok(()),
ExitusPraedicati::Falsum { description } => Err(description),
}
}
}
#[inline]
pub fn check_predicate<T, P: Praedicatum<T>>(value: &T) -> ExitusPraedicati {
if P::check(value) {
ExitusPraedicati::Verum
} else {
ExitusPraedicati::Falsum {
description: P::description(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct IsPositive;
impl Praedicatum<i32> for IsPositive {
fn check(value: &i32) -> bool {
*value > 0
}
fn description() -> &'static str {
"value must be positive"
}
}
#[test]
fn test_praedicatum_check() {
assert!(IsPositive::check(&42));
assert!(!IsPositive::check(&0));
assert!(!IsPositive::check(&-1));
}
#[test]
fn test_praedicatum_description() {
assert_eq!(IsPositive::description(), "value must be positive");
}
#[test]
fn test_validatus() {
assert!(<i32 as Validatus<IsPositive>>::is_valid(&42i32));
assert!(!<i32 as Validatus<IsPositive>>::is_valid(&0i32));
assert!(<i32 as Validatus<IsPositive>>::validate(&42i32).is_ok());
assert!(<i32 as Validatus<IsPositive>>::validate(&0i32).is_err());
}
#[test]
fn test_evidentia_verify() {
let evidence = EvidentiaPredicati::<i32, IsPositive>::verify(&42);
assert!(evidence.is_some());
let no_evidence = EvidentiaPredicati::<i32, IsPositive>::verify(&-1);
assert!(no_evidence.is_none());
}
#[test]
fn test_exitus_praedicati() {
let result = check_predicate::<i32, IsPositive>(&42);
assert!(result.passed());
assert!(!result.failed());
assert!(result.to_result().is_ok());
let result = check_predicate::<i32, IsPositive>(&-1);
assert!(!result.passed());
assert!(result.failed());
assert!(result.to_result().is_err());
}
}