unimock 0.4.9

A versatile and developer-friendly trait mocking library
Documentation
use std::{collections::HashSet, fmt::Display};

use crate::{
    call_pattern::{InputIndex, PatIndex},
    macro_api::MismatchReporter,
};

#[derive(Clone)]
pub(crate) struct Mismatches {
    mismatches: Vec<(PatIndex, InputIndex, Mismatch)>,
}

impl Mismatches {
    pub fn new() -> Self {
        Self { mismatches: vec![] }
    }

    pub fn collect_from_reporter(&mut self, pat_index: PatIndex, reporter: MismatchReporter) {
        for (input_index, mismatch) in reporter.mismatches {
            self.mismatches.push((pat_index, input_index, mismatch));
        }
    }

    fn has_unique_pat_index(&self) -> bool {
        let mut pat_indexes = HashSet::new();
        for (pat_index, _, _) in &self.mismatches {
            pat_indexes.insert(pat_index.0);
        }

        pat_indexes.len() <= 1
    }
}

impl Display for Mismatches {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if !self.mismatches.is_empty() {
            writeln!(f)?;
        }

        let is_unique_pat = self.has_unique_pat_index();

        for (pat_index, input_index, mismatch) in &self.mismatches {
            let Mismatch {
                kind,
                actual,
                expected,
            } = mismatch;
            let mut header_msg = MismatchMsg::new(*pat_index, *input_index, is_unique_pat, *kind);

            match (kind, actual) {
                (MismatchKind::Pattern, Some(actual)) => {
                    header_msg.has_comparison = true;
                    header_msg.fmt(f)?;
                    Diff::new(actual, expected).fmt(f)?;
                }
                (MismatchKind::Eq, Some(actual)) => {
                    if actual == expected {
                        header_msg.fmt(f)?;

                        write!(f, "Actual value did not equal expected value, but their Debug representation are identical:")?;
                        write!(f, "{actual}")?;
                    } else {
                        header_msg.has_comparison = true;
                        header_msg.fmt(f)?;

                        Diff::new(actual, expected).fmt(f)?;
                    }
                }
                (MismatchKind::Ne, Some(actual)) => {
                    if actual == expected {
                        header_msg.fmt(f)?;
                        write!(f, "{actual}")?;
                    } else {
                        header_msg.has_comparison = true;
                        header_msg.fmt(f)?;

                        writeln!(f, "(Warning) Debug representation problem: Expected and actual asserted inequality failed, though Debug representations differ:")?;
                        Diff::new(actual, expected).fmt(f)?;
                    }
                }
                (MismatchKind::Pattern, None) => {
                    header_msg.fmt(f)?;
                    writeln!(f, "Actual value did not match expected pattern, but can't display diagnostics because the type is likely missing #[derive(Debug)].")?;
                }
                (MismatchKind::Eq, None) => {
                    header_msg.fmt(f)?;
                    writeln!(f, "Actual value did not equal expected value, but can't display diagnostics because the type is likely missing #[derive(Debug)].")?;
                }
                (MismatchKind::Ne, None) => {
                    header_msg.fmt(f)?;
                    writeln!(f, "Actual value unexpectedly equalled expected value, but can't display diagnostics because the type is likely missing #[derive(Debug)].")?;
                }
            }
        }

        Ok(())
    }
}

#[derive(Clone)]
pub(crate) struct Mismatch {
    pub kind: MismatchKind,
    pub actual: Option<String>,
    pub expected: String,
}

#[derive(Clone, Copy)]
pub(crate) enum MismatchKind {
    Pattern,
    Eq,
    Ne,
}

struct MismatchMsg {
    pat_index: PatIndex,
    input_index: InputIndex,
    is_unique_pat: bool,
    mismatch_kind: MismatchKind,
    has_comparison: bool,
}

impl MismatchMsg {
    fn new(
        pat_index: PatIndex,
        input_index: InputIndex,
        is_unique_pat: bool,
        mismatch_kind: MismatchKind,
    ) -> Self {
        Self {
            pat_index,
            input_index,
            is_unique_pat,
            mismatch_kind,
            has_comparison: false,
        }
    }
}

impl Display for MismatchMsg {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let initial_msg = match self.mismatch_kind {
            MismatchKind::Pattern => "Pattern mismatch for ",
            MismatchKind::Eq => "Equality mismatch for ",
            MismatchKind::Ne => "Inequality mismatch for ",
        };

        write!(f, "{initial_msg}")?;

        if self.is_unique_pat {
            write!(f, "input #{}", self.input_index.0)?;
        } else {
            write!(
                f,
                "call pattern #{}, input #{}",
                self.pat_index.0, self.input_index.0
            )?;
        }

        if let MismatchKind::Pattern | MismatchKind::Eq = self.mismatch_kind {
            if self.has_comparison {
                write!(f, " (actual / expected)")?;
            }
        }

        writeln!(f, ":")?;
        Ok(())
    }
}

struct Diff<'s> {
    actual: &'s str,
    expected: &'s str,
}

impl<'s> Diff<'s> {
    fn new(actual: &'s impl AsRef<str>, expected: &'s impl AsRef<str>) -> Self {
        Self {
            actual: actual.as_ref(),
            expected: expected.as_ref(),
        }
    }
}

impl<'s> Display for Diff<'s> {
    #[cfg(feature = "pretty-print")]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let comparison = pretty_assertions::StrComparison::new(self.actual, self.expected);
        write!(f, "{comparison}")
    }

    #[cfg(not(feature = "pretty-print"))]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "  actual: {}", self.actual)?;
        write!(f, "expected: {}", self.expected)?;
        Ok(())
    }
}