use std::convert::TryInto;
use crate::matchers::map::IterMap;
use super::{
AssertionFailure, AssertionFormat, DynTransformMatch, FormattedOutput, MatchError, MatchOutcome,
};
#[derive(Debug)]
pub struct Assertion<In, AssertFmt>
where
AssertFmt: AssertionFormat,
{
value: In,
ctx: AssertFmt::Context,
formatter: AssertFmt,
}
fn fail<Ctx, AssertFmt>(ctx: Ctx, error: MatchError, format: AssertFmt) -> !
where
AssertFmt: AssertionFormat<Context = Ctx>,
{
FormattedOutput::new(AssertionFailure { ctx, error }, format)
.expect("failed to format matcher output")
.fail();
}
impl<In, AssertFmt> Assertion<In, AssertFmt>
where
AssertFmt: AssertionFormat + Default,
{
pub fn new(value: In, ctx: AssertFmt::Context) -> Self {
Self {
value,
ctx,
formatter: AssertFmt::default(),
}
}
}
impl<In, AssertFmt> Assertion<In, AssertFmt>
where
AssertFmt: AssertionFormat,
{
pub fn to<Out>(
self,
matcher: impl DynTransformMatch<In = In, PosOut = Out>,
) -> Assertion<Out, AssertFmt> {
match Box::new(matcher).match_pos(self.value) {
Ok(MatchOutcome::Success(out)) => Assertion {
value: out,
formatter: self.formatter,
ctx: self.ctx,
},
Ok(MatchOutcome::Fail(result)) => {
fail(self.ctx, MatchError::Fail(result), self.formatter)
}
Err(error) => fail(self.ctx, MatchError::Err(error), self.formatter),
}
}
pub fn to_not<Out>(
self,
matcher: impl DynTransformMatch<In = In, NegOut = Out>,
) -> Assertion<Out, AssertFmt> {
match Box::new(matcher).match_neg(self.value) {
Ok(MatchOutcome::Success(out)) => Assertion {
value: out,
formatter: self.formatter,
ctx: self.ctx,
},
Ok(MatchOutcome::Fail(result)) => {
fail(self.ctx, MatchError::Fail(result), self.formatter)
}
Err(error) => fail(self.ctx, MatchError::Err(error), self.formatter),
}
}
pub fn map<Out>(self, func: impl FnOnce(In) -> Out) -> Assertion<Out, AssertFmt> {
Assertion {
value: func(self.value),
formatter: self.formatter,
ctx: self.ctx,
}
}
pub fn try_map<Out>(
self,
func: impl FnOnce(In) -> crate::Result<Out>,
) -> Assertion<Out, AssertFmt> {
match func(self.value) {
Ok(out) => Assertion {
value: out,
formatter: self.formatter,
ctx: self.ctx,
},
Err(error) => fail(self.ctx, MatchError::Err(error), self.formatter),
}
}
pub fn into<Out>(self) -> Assertion<Out, AssertFmt>
where
Out: From<In>,
{
Assertion {
value: self.value.into(),
formatter: self.formatter,
ctx: self.ctx,
}
}
pub fn try_into<Out>(self) -> Assertion<Out, AssertFmt>
where
Out: TryFrom<In>,
<Out as TryFrom<In>>::Error: std::error::Error + Send + Sync + 'static,
{
Assertion {
value: match self.value.try_into() {
Ok(out) => out,
Err(error) => fail(self.ctx, MatchError::Err(error.into()), self.formatter),
},
formatter: self.formatter,
ctx: self.ctx,
}
}
pub fn into_inner(self) -> In {
self.value
}
pub fn ctx(&self) -> &AssertFmt::Context {
&self.ctx
}
pub fn ctx_mut(&mut self) -> &mut AssertFmt::Context {
&mut self.ctx
}
pub fn fmt(&self) -> &AssertFmt {
&self.formatter
}
pub fn fmt_mut(&mut self) -> &mut AssertFmt {
&mut self.formatter
}
}
impl<In, AssertFmt> Assertion<In, AssertFmt>
where
In: IntoIterator,
AssertFmt: AssertionFormat,
{
pub fn iter_map<'a, Out>(
self,
func: impl Fn(In::Item) -> Out + 'a,
) -> Assertion<IterMap<'a, In::Item, Out, In::IntoIter>, AssertFmt> {
Assertion {
value: IterMap::new(self.value.into_iter(), Box::new(func)),
formatter: self.formatter,
ctx: self.ctx,
}
}
pub fn iter_try_map<'a, Out>(
self,
func: impl Fn(In::Item) -> crate::Result<Out> + 'a,
) -> Assertion<Vec<Out>, AssertFmt> {
let mapped_values = self
.value
.into_iter()
.map(func)
.collect::<Result<Vec<_>, _>>();
Assertion {
value: match mapped_values {
Ok(vec) => vec,
Err(error) => fail(self.ctx, MatchError::Err(error), self.formatter),
},
formatter: self.formatter,
ctx: self.ctx,
}
}
}
#[macro_export]
macro_rules! expect {
($actual:expr) => {
$crate::core::Assertion::<_, $crate::core::DefaultAssertionFormat>::new($actual, {
let mut ctx = <$crate::core::AssertionContext as ::std::default::Default>::default();
ctx.expr =
::std::option::Option::Some(::std::string::String::from(stringify!($actual)));
ctx.location = ::std::option::Option::Some($crate::core::FileLocation {
file: ::std::string::String::from(file!()),
line: line!(),
column: column!(),
});
ctx
})
};
}