Skip to content

Commit

Permalink
feat: Extend Predicate to report cause
Browse files Browse the repository at this point in the history
This is the last of the foundation for resolving assert-rs#7.
  • Loading branch information
epage committed Jul 21, 2018
1 parent 8759f14 commit cadd280
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,17 @@ pub trait Predicate<Item: ?Sized>: reflection::PredicateReflection {
/// Execute this `Predicate` against `variable`, returning the resulting
/// boolean.
fn eval(&self, variable: &Item) -> bool;

/// Find a case that proves this predicate as `expected` when run against `variable`.
fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option<reflection::Case<'a>>
where
Self: Sized,
{
let actual = self.eval(variable);
if expected == actual {
Some(reflection::Case::new(Some(self), actual))
} else {
None
}
}
}
162 changes: 162 additions & 0 deletions src/reflection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

//! Introspect into the state of a `Predicate`.

use std::borrow;
use std::fmt;
use std::slice;

/// Introspect the state of a `Predicate`.
pub trait PredicateReflection: fmt::Display {
Expand Down Expand Up @@ -96,6 +98,166 @@ impl<'a> fmt::Debug for Child<'a> {
}
}

/// A descriptive explanation for why a predicate failed.
pub struct Case<'a> {
predicate: Option<&'a PredicateReflection>,
result: bool,
products: Vec<Product>,
children: Vec<Case<'a>>,
}

impl<'a> Case<'a> {
/// Create a new `Case` describing the result of a `Predicate`.
pub fn new(predicate: Option<&'a PredicateReflection>, result: bool) -> Self {
Self {
predicate,
result,
products: Default::default(),
children: Default::default(),
}
}

/// Add an additional by product to a `Case`.
pub fn add_product(mut self, product: Product) -> Self {
self.products.push(product);
self
}

/// Add an additional by product to a `Case`.
pub fn add_child(mut self, child: Case<'a>) -> Self {
self.children.push(child);
self
}

/// The `Predicate` that produced this case.
pub fn predicate(&self) -> Option<&PredicateReflection> {
self.predicate
}

/// The result of this case.
pub fn result(&self) -> bool {
self.result
}

/// Access the by-products from determining this case.
pub fn products(&self) -> CaseProducts {
CaseProducts {
0: self.products.iter(),
}
}

/// Access the sub-cases.
pub fn children(&self) -> CaseChildren {
CaseChildren {
0: self.children.iter(),
}
}
}

impl<'a> fmt::Debug for Case<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let predicate = if let Some(ref predicate) = self.predicate {
format!("Some({})", predicate)
} else {
"None".to_owned()
};
f.debug_struct("Case")
.field("predicate", &predicate)
.field("result", &self.result)
.field("products", &self.products)
.field("children", &self.children)
.finish()
}
}

/// Iterator over a `Case`s by-products.
#[derive(Debug, Clone)]
pub struct CaseProducts<'a>(slice::Iter<'a, Product>);

impl<'a> Iterator for CaseProducts<'a> {
type Item = &'a Product;

fn next(&mut self) -> Option<&'a Product> {
self.0.next()
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}

fn count(self) -> usize {
self.0.count()
}
}

/// Iterator over a `Case`s sub-cases.
#[derive(Debug, Clone)]
pub struct CaseChildren<'a>(slice::Iter<'a, Case<'a>>);

impl<'a> Iterator for CaseChildren<'a> {
type Item = &'a Case<'a>;

fn next(&mut self) -> Option<&'a Case<'a>> {
self.0.next()
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}

fn count(self) -> usize {
self.0.count()
}
}

/// A by-product of a predicate evaluation.
///
/// ```rust
/// use predicates;
///
/// let product = predicates::reflection::Product::new("key", "value");
/// println!("{}", product);
/// let product = predicates::reflection::Product::new(format!("key-{}", 5), 30);
/// println!("{}", product);
/// ```
pub struct Product(borrow::Cow<'static, str>, Box<fmt::Display>);

impl Product {
/// Create a new `Product`.
pub fn new<S, D>(key: S, value: D) -> Self
where
S: Into<borrow::Cow<'static, str>>,
D: fmt::Display + 'static,
{
Self {
0: key.into(),
1: Box::new(value),
}
}

/// Access the `Product` name.
pub fn name(&self) -> &str {
self.0.as_ref()
}

/// Access the `Product` value.
pub fn value(&self) -> &fmt::Display {
&self.1
}
}

impl<'a> fmt::Display for Product {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.0, self.1)
}
}

impl<'a> fmt::Debug for Product {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({:?}, {})", self.0, self.1)
}
}

#[derive(Clone, PartialEq, Eq)]
pub(crate) struct DebugAdapter<T>
where
Expand Down

0 comments on commit cadd280

Please sign in to comment.