use core::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LawViolation {
pub handler: &'static str,
pub law: &'static str,
pub description: &'static str,
}
impl fmt::Display for LawViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Handler '{}' violates '{}' law: {}",
self.handler, self.law, self.description
)
}
}
pub trait VerifiedHandler {
fn handler_name() -> &'static str;
fn verify_laws() -> Result<(), LawViolation>;
#[cfg(debug_assertions)]
fn assert_laws() {
if let Err(violation) = Self::verify_laws() {
crate::cold_panic!("{}", violation);
}
}
#[cfg(not(debug_assertions))]
fn assert_laws() {}
}
#[derive(Clone, Debug)]
pub struct VerificationResult {
pub handler: &'static str,
pub laws_checked: usize,
pub violations: alloc::vec::Vec<LawViolation>,
}
impl VerificationResult {
pub fn new(handler: &'static str) -> Self {
VerificationResult {
handler,
laws_checked: 0,
violations: alloc::vec::Vec::new(),
}
}
pub fn check_passed(&mut self) {
self.laws_checked += 1;
}
pub fn check_failed(&mut self, law: &'static str, description: &'static str) {
self.laws_checked += 1;
self.violations.push(LawViolation {
handler: self.handler,
law,
description,
});
}
pub fn is_ok(&self) -> bool {
self.violations.is_empty()
}
pub fn to_result(self) -> Result<(), LawViolation> {
if let Some(v) = self.violations.into_iter().next() {
Err(v)
} else {
Ok(())
}
}
}
#[cfg(debug_assertions)]
use crate::nexus::effects::state::StatefulComputation;
#[cfg(debug_assertions)]
pub fn verify_state_handler<S: Clone + PartialEq + Default + 'static>() -> VerificationResult {
let mut result = VerificationResult::new("State");
let get_put =
StatefulComputation::<S, S>::get().and_then(|s| StatefulComputation::<S, ()>::put(s));
let pure_unit = StatefulComputation::<S, ()>::pure(());
let initial = S::default();
let (_r1, s1) = get_put.run(initial.clone());
let (_r2, s2) = pure_unit.run(initial);
if s1 == s2 {
result.check_passed();
} else {
result.check_failed("Get-Put", "get.and_then(put) != pure(())");
}
result
}
#[cfg(not(debug_assertions))]
pub fn verify_state_handler<S>() -> VerificationResult {
VerificationResult::new("State")
}
#[cfg(debug_assertions)]
use crate::nexus::effects::reader::ReaderComputation;
#[cfg(debug_assertions)]
pub fn verify_reader_handler<E: Clone + PartialEq + Default + 'static>() -> VerificationResult {
let mut result = VerificationResult::new("Reader");
let env = E::default();
let ask_ask = ReaderComputation::<E, E>::ask()
.and_then(move |e| ReaderComputation::<E, E>::ask().map(move |_| e));
let just_ask = ReaderComputation::<E, E>::ask();
if ask_ask.run(&env) == just_ask.run(&env) {
result.check_passed();
} else {
result.check_failed("Ask-Ask", "ask.and_then(|e| ask.map(|_| e)) != ask");
}
result
}
#[cfg(not(debug_assertions))]
pub fn verify_reader_handler<E>() -> VerificationResult {
VerificationResult::new("Reader")
}
#[cfg(debug_assertions)]
use crate::nexus::effects::error::ErrorComputation;
#[cfg(debug_assertions)]
pub fn verify_error_handler<E: Clone + PartialEq + Default, A: Clone + PartialEq + Default>()
-> VerificationResult {
let mut result = VerificationResult::new("Error");
let value = A::default();
let pure_comp = ErrorComputation::<E, A>::ok(value.clone());
let caught = pure_comp.or_else(|_| ErrorComputation::ok(value.clone()));
if caught.run() == Ok(value.clone()) {
result.check_passed();
} else {
result.check_failed("Catch-Pure", "catch(pure(x), h) != pure(x)");
}
let error = E::default();
let throw_bind = ErrorComputation::<E, A>::err(error.clone())
.and_then(|_: A| ErrorComputation::<E, A>::ok(value));
if throw_bind.run() == Err(error) {
result.check_passed();
} else {
result.check_failed("Throw-Bind", "throw(e).and_then(f) != throw(e)");
}
result
}
#[cfg(not(debug_assertions))]
pub fn verify_error_handler<E, A>() -> VerificationResult {
VerificationResult::new("Error")
}
#[cfg(debug_assertions)]
use crate::nexus::effects::writer::{Monoid, WriterComputation};
#[cfg(debug_assertions)]
pub fn verify_writer_handler<W: Monoid + PartialEq + 'static>() -> VerificationResult {
let mut result = VerificationResult::new("Writer");
let tell_empty = WriterComputation::<W, ()>::tell(W::empty());
let pure_unit = WriterComputation::<W, ()>::pure(());
let (_r1, w1) = tell_empty.run();
let (_r2, w2) = pure_unit.run();
if w1 == w2 {
result.check_passed();
} else {
result.check_failed("Tell-Empty", "tell(empty) != pure(())");
}
result
}
#[cfg(not(debug_assertions))]
pub fn verify_writer_handler<W>() -> VerificationResult {
VerificationResult::new("Writer")
}
#[cfg(debug_assertions)]
pub fn verify_all_handlers() -> alloc::vec::Vec<VerificationResult> {
alloc::vec![
verify_state_handler::<i32>(),
verify_reader_handler::<i32>(),
verify_error_handler::<&str, i32>(),
verify_writer_handler::<alloc::string::String>(),
]
}
#[cfg(not(debug_assertions))]
pub fn verify_all_handlers() -> alloc::vec::Vec<VerificationResult> {
alloc::vec::Vec::new()
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn test_verify_state_handler() {
let result = verify_state_handler::<i32>();
assert!(result.is_ok(), "State handler should satisfy laws");
}
#[test]
fn test_verify_reader_handler() {
let result = verify_reader_handler::<i32>();
assert!(result.is_ok(), "Reader handler should satisfy laws");
}
#[test]
fn test_verify_error_handler() {
let result = verify_error_handler::<&str, i32>();
assert!(result.is_ok(), "Error handler should satisfy laws");
}
#[test]
fn test_verify_writer_handler() {
let result = verify_writer_handler::<alloc::string::String>();
assert!(result.is_ok(), "Writer handler should satisfy laws");
}
#[test]
fn test_verify_all_handlers() {
let results = verify_all_handlers();
for result in results {
assert!(
result.is_ok(),
"{} handler failed: {:?}",
result.handler,
result.violations
);
}
}
#[test]
fn test_law_violation_display() {
let violation = LawViolation {
handler: "TestHandler",
law: "TestLaw",
description: "expected X but got Y",
};
let msg = violation.to_string();
assert!(msg.contains("TestHandler"));
assert!(msg.contains("TestLaw"));
assert!(msg.contains("expected X but got Y"));
}
}