gunnar-sendpack 1.1.0

git's receive-pack wire format, both ends: the send-pack conversation gitoxide does not have, plus the server-side encoders for the same grammar. Plumbing only, no gunnar types.
Documentation
//! One error type for the whole conversation.

use std::fmt;

/// What went wrong.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// The peer put something on the wire this grammar cannot read.
    #[error("{0}")]
    Protocol(String),
    /// The caller asked for something that cannot be put on the wire.
    ///
    /// Separated from [`Error::Protocol`] because the two have different
    /// audiences: a protocol error is the remote's fault and belongs in a
    /// diagnostic, an invalid request is the caller's and belongs in a
    /// compile-time-adjacent complaint.
    #[error("{0}")]
    Invalid(String),
    /// The peer used a hash algorithm this repository does not.
    ///
    /// A variant of its own rather than a [`Error::Protocol`] string, because
    /// it is the one protocol error a caller routinely wants to *re-report* in
    /// its own vocabulary — an `object-format` mismatch is what a server tells
    /// a client in its status report, and digging two algorithm names back out
    /// of a formatted message to do so is how a message becomes an API by
    /// accident.
    #[error("an object id is {theirs} but the repository is {ours}")]
    ObjectFormat {
        /// The repository's algorithm, as it appears in `object-format=`.
        ours: &'static str,
        /// The peer's.
        theirs: &'static str,
    },
    /// The remote refused the conversation and said why, in an `ERR <msg>`
    /// packet or on sideband 3.
    ///
    /// Its own variant rather than an [`Error::Protocol`] string because it is
    /// **not** a protocol error: the grammar was obeyed exactly. `ERR` is the
    /// one line in the advertisement that is a sentence for a human rather than
    /// a record for a parser, and reading it as a ref turns
    /// `you are not allowed to push here` into
    /// `bad object id "ERR" for ref "you are not allowed to push here"`.
    ///
    /// The wording is git's — `connect.c` answers an `ERR` packet with
    /// `die(_("remote error: %s"), arg)` — so an operator who has seen the
    /// message from git does not have to learn a second phrase for it. Measured
    /// against git 2.53.0: `fatal: remote error: you are not allowed to push
    /// here`, exit 128.
    #[error("remote error: {0}")]
    Remote(String),
    /// The socket, the pipe or the file underneath.
    #[error("io: {0}")]
    Io(#[from] std::io::Error),
}

impl Error {
    /// A protocol error from anything printable.
    pub fn protocol(message: impl fmt::Display) -> Self {
        Error::Protocol(message.to_string())
    }

    /// An invalid-request error from anything printable.
    pub fn invalid(message: impl fmt::Display) -> Self {
        Error::Invalid(message.to_string())
    }
}

/// The crate's result type.
pub type Result<T> = std::result::Result<T, Error>;