zenops 0.16.0

Declarative system configuration management for shell config and dotfiles.
Documentation
//! `git`-scoped error type.
//!
//! Failure modes specific to parsing `git` output. `xshell::Error` (the
//! exit-status / I/O failures from invoking `git` itself) is deliberately
//! NOT wrapped here — it already has a typed home at the crate level
//! (`crate::Error::Shell`) and at the per-flow per-module errors that own
//! particular invocations (`InitError::CloneFailed`,
//! `InitError::GitInitFailed`). Adding a third path would only blur which
//! variant a caller should match on.
//!
//! Exposed to the rest of the crate as `crate::Error::Git` via
//! `#[error(transparent)]` + `#[from]`.

/// Failure modes for the parsers in [`super`].
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// A line of `git status --porcelain=v2` didn't have the column count
    /// the format mandates. Real git won't emit this; surfaces if the
    /// output is truncated, mangled by a wrapper, or generated by something
    /// that isn't actually `git`.
    #[error("git status --porcelain=v2: malformed line {line:?} ({reason})")]
    PorcelainParse {
        /// The full porcelain line that failed to parse.
        line: String,
        /// Static description of which column was missing.
        reason: &'static str,
    },
}

impl PartialEq for Error {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (
                Self::PorcelainParse {
                    line: l_line,
                    reason: l_reason,
                },
                Self::PorcelainParse {
                    line: r_line,
                    reason: r_reason,
                },
            ) => l_line == r_line && l_reason == r_reason,
        }
    }
}

#[cfg(test)]
mod tests {
    use similar_asserts::assert_eq;

    use super::*;

    #[test]
    fn porcelain_parse_eq_compares_line_and_reason() {
        let a = Error::PorcelainParse {
            line: "1 M.".to_string(),
            reason: "truncated",
        };
        let b = Error::PorcelainParse {
            line: "1 M.".to_string(),
            reason: "truncated",
        };
        let c = Error::PorcelainParse {
            line: "1 M.".to_string(),
            reason: "missing tag",
        };
        let d = Error::PorcelainParse {
            line: "different".to_string(),
            reason: "truncated",
        };
        assert_eq!(a, b);
        assert_ne!(a, c);
        assert_ne!(a, d);
    }
}