use core::fmt;
use crate::error::MatchError;
#[derive(Debug)]
#[non_exhaustive]
pub struct Expectation<T> {
value: T,
pub expression: &'static str,
pub negated: bool,
file: Option<&'static str>,
line: Option<u32>,
}
impl<T> Expectation<T> {
pub const fn new(value: T, expression: &'static str) -> Self {
Self {
value,
expression,
negated: false,
file: None,
line: None,
}
}
pub const fn new_located(
value: T,
expression: &'static str,
file: &'static str,
line: u32,
) -> Self {
Self {
value,
expression,
negated: false,
file: Some(file),
line: Some(line),
}
}
#[must_use]
pub const fn negate(mut self) -> Self {
self.negated = !self.negated;
self
}
#[must_use]
pub const fn not(self) -> Self {
self.negate()
}
pub const fn value(&self) -> &T {
&self.value
}
pub fn into_value(self) -> T {
self.value
}
}
impl<T: fmt::Debug> Expectation<T> {
pub fn check(&self, is_match: bool, expected: impl fmt::Display) -> Result<(), MatchError> {
let pass = if self.negated { !is_match } else { is_match };
if pass {
return Ok(());
}
Err(MatchError::new(
self.expression.to_string(),
expected.to_string(),
format!("{:?}", self.value()),
self.negated,
)
.with_location(self.file, self.line))
}
pub fn to_satisfy(
&self,
predicate: impl FnOnce(&T) -> bool,
description: impl fmt::Display,
) -> Result<(), MatchError> {
self.check(predicate(self.value()), description)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_stores_value_and_expression() {
let exp = Expectation::new(42, "42");
assert_eq!(*exp.value(), 42);
assert_eq!(exp.expression, "42");
assert!(!exp.negated);
}
#[test]
fn negate_toggles_flag() {
let exp = Expectation::new(1, "1").negate();
assert!(exp.negated);
}
#[test]
fn double_negate_returns_to_original() {
let exp = Expectation::new(1, "1").negate().negate();
assert!(!exp.negated);
}
#[test]
fn not_is_alias_for_negate() {
let exp = Expectation::new(1, "1").not();
assert!(exp.negated);
}
#[test]
fn value_returns_reference() {
let exp = Expectation::new("hello", "s");
assert_eq!(*exp.value(), "hello");
}
#[test]
fn check_pass() {
let exp = Expectation::new(42, "42");
assert!(exp.check(true, "42").is_ok());
}
#[test]
fn check_fail() {
let exp = Expectation::new(42, "42");
assert!(exp.check(false, "99").is_err());
}
#[test]
fn check_negated_pass() {
let exp = Expectation::new(42, "42").negate();
assert!(exp.check(false, "99").is_ok());
}
#[test]
fn check_negated_fail() {
let exp = Expectation::new(42, "42").negate();
assert!(exp.check(true, "42").is_err());
}
#[test]
fn to_satisfy_pass() {
assert!(Expectation::new(7, "7")
.to_satisfy(|x| x % 2 != 0, "to be odd")
.is_ok());
}
#[test]
fn to_satisfy_fail() {
assert!(Expectation::new(4, "4")
.to_satisfy(|x| x % 2 != 0, "to be odd")
.is_err());
}
#[test]
fn to_satisfy_negated_pass() {
assert!(Expectation::new(4, "4")
.negate()
.to_satisfy(|x| x % 2 != 0, "to be odd")
.is_ok());
}
#[test]
fn to_satisfy_negated_fail() {
assert!(Expectation::new(7, "7")
.negate()
.to_satisfy(|x| x % 2 != 0, "to be odd")
.is_err());
}
#[test]
fn into_value_returns_inner() {
let exp = Expectation::new(42, "42");
assert_eq!(exp.into_value(), 42);
}
#[test]
fn new_located_sets_fields() {
let exp = Expectation::new_located(1, "1", "test.rs", 10);
assert_eq!(*exp.value(), 1);
assert_eq!(exp.expression, "1");
assert!(!exp.negated);
}
#[test]
fn to_satisfy_accepts_display_description() {
let desc = format!("to be divisible by {}", 3);
assert!(Expectation::new(9, "9")
.to_satisfy(|x| x % 3 == 0, desc)
.is_ok());
}
}