Skip to content

Commit cadd280

Browse files
committed
feat: Extend Predicate to report cause
This is the last of the foundation for resolving assert-rs#7.
1 parent 8759f14 commit cadd280

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

src/core.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,17 @@ pub trait Predicate<Item: ?Sized>: reflection::PredicateReflection {
1919
/// Execute this `Predicate` against `variable`, returning the resulting
2020
/// boolean.
2121
fn eval(&self, variable: &Item) -> bool;
22+
23+
/// Find a case that proves this predicate as `expected` when run against `variable`.
24+
fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option<reflection::Case<'a>>
25+
where
26+
Self: Sized,
27+
{
28+
let actual = self.eval(variable);
29+
if expected == actual {
30+
Some(reflection::Case::new(Some(self), actual))
31+
} else {
32+
None
33+
}
34+
}
2235
}

src/reflection.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
//! Introspect into the state of a `Predicate`.
1010
11+
use std::borrow;
1112
use std::fmt;
13+
use std::slice;
1214

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

101+
/// A descriptive explanation for why a predicate failed.
102+
pub struct Case<'a> {
103+
predicate: Option<&'a PredicateReflection>,
104+
result: bool,
105+
products: Vec<Product>,
106+
children: Vec<Case<'a>>,
107+
}
108+
109+
impl<'a> Case<'a> {
110+
/// Create a new `Case` describing the result of a `Predicate`.
111+
pub fn new(predicate: Option<&'a PredicateReflection>, result: bool) -> Self {
112+
Self {
113+
predicate,
114+
result,
115+
products: Default::default(),
116+
children: Default::default(),
117+
}
118+
}
119+
120+
/// Add an additional by product to a `Case`.
121+
pub fn add_product(mut self, product: Product) -> Self {
122+
self.products.push(product);
123+
self
124+
}
125+
126+
/// Add an additional by product to a `Case`.
127+
pub fn add_child(mut self, child: Case<'a>) -> Self {
128+
self.children.push(child);
129+
self
130+
}
131+
132+
/// The `Predicate` that produced this case.
133+
pub fn predicate(&self) -> Option<&PredicateReflection> {
134+
self.predicate
135+
}
136+
137+
/// The result of this case.
138+
pub fn result(&self) -> bool {
139+
self.result
140+
}
141+
142+
/// Access the by-products from determining this case.
143+
pub fn products(&self) -> CaseProducts {
144+
CaseProducts {
145+
0: self.products.iter(),
146+
}
147+
}
148+
149+
/// Access the sub-cases.
150+
pub fn children(&self) -> CaseChildren {
151+
CaseChildren {
152+
0: self.children.iter(),
153+
}
154+
}
155+
}
156+
157+
impl<'a> fmt::Debug for Case<'a> {
158+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159+
let predicate = if let Some(ref predicate) = self.predicate {
160+
format!("Some({})", predicate)
161+
} else {
162+
"None".to_owned()
163+
};
164+
f.debug_struct("Case")
165+
.field("predicate", &predicate)
166+
.field("result", &self.result)
167+
.field("products", &self.products)
168+
.field("children", &self.children)
169+
.finish()
170+
}
171+
}
172+
173+
/// Iterator over a `Case`s by-products.
174+
#[derive(Debug, Clone)]
175+
pub struct CaseProducts<'a>(slice::Iter<'a, Product>);
176+
177+
impl<'a> Iterator for CaseProducts<'a> {
178+
type Item = &'a Product;
179+
180+
fn next(&mut self) -> Option<&'a Product> {
181+
self.0.next()
182+
}
183+
184+
fn size_hint(&self) -> (usize, Option<usize>) {
185+
self.0.size_hint()
186+
}
187+
188+
fn count(self) -> usize {
189+
self.0.count()
190+
}
191+
}
192+
193+
/// Iterator over a `Case`s sub-cases.
194+
#[derive(Debug, Clone)]
195+
pub struct CaseChildren<'a>(slice::Iter<'a, Case<'a>>);
196+
197+
impl<'a> Iterator for CaseChildren<'a> {
198+
type Item = &'a Case<'a>;
199+
200+
fn next(&mut self) -> Option<&'a Case<'a>> {
201+
self.0.next()
202+
}
203+
204+
fn size_hint(&self) -> (usize, Option<usize>) {
205+
self.0.size_hint()
206+
}
207+
208+
fn count(self) -> usize {
209+
self.0.count()
210+
}
211+
}
212+
213+
/// A by-product of a predicate evaluation.
214+
///
215+
/// ```rust
216+
/// use predicates;
217+
///
218+
/// let product = predicates::reflection::Product::new("key", "value");
219+
/// println!("{}", product);
220+
/// let product = predicates::reflection::Product::new(format!("key-{}", 5), 30);
221+
/// println!("{}", product);
222+
/// ```
223+
pub struct Product(borrow::Cow<'static, str>, Box<fmt::Display>);
224+
225+
impl Product {
226+
/// Create a new `Product`.
227+
pub fn new<S, D>(key: S, value: D) -> Self
228+
where
229+
S: Into<borrow::Cow<'static, str>>,
230+
D: fmt::Display + 'static,
231+
{
232+
Self {
233+
0: key.into(),
234+
1: Box::new(value),
235+
}
236+
}
237+
238+
/// Access the `Product` name.
239+
pub fn name(&self) -> &str {
240+
self.0.as_ref()
241+
}
242+
243+
/// Access the `Product` value.
244+
pub fn value(&self) -> &fmt::Display {
245+
&self.1
246+
}
247+
}
248+
249+
impl<'a> fmt::Display for Product {
250+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251+
write!(f, "{}: {}", self.0, self.1)
252+
}
253+
}
254+
255+
impl<'a> fmt::Debug for Product {
256+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257+
write!(f, "({:?}, {})", self.0, self.1)
258+
}
259+
}
260+
99261
#[derive(Clone, PartialEq, Eq)]
100262
pub(crate) struct DebugAdapter<T>
101263
where

0 commit comments

Comments
 (0)