1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use crate::{encode, Channel, ERR_PREFIX};
use bstr::BStr;
use std::io;

/// A borrowed packet line as it refers to a slice of data by reference.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Borrowed<'a> {
    /// A chunk of raw data.
    Data(&'a [u8]),
    /// A flush packet.
    Flush,
    /// A delimiter packet.
    Delimiter,
    /// The end of the response.
    ResponseEnd,
}

impl<'a> Borrowed<'a> {
    /// Serialize this instance to `out` in git `packetline` format, returning the amount of bytes written to `out`.
    pub fn to_write(&self, out: impl io::Write) -> Result<usize, encode::Error> {
        match self {
            Borrowed::Data(d) => encode::data_to_write(d, out),
            Borrowed::Flush => encode::flush_to_write(out).map_err(Into::into),
            Borrowed::Delimiter => encode::delim_to_write(out).map_err(Into::into),
            Borrowed::ResponseEnd => encode::response_end_to_write(out).map_err(Into::into),
        }
    }

    /// Return this instance as slice if it's [`Data`][Borrowed::Data].
    pub fn as_slice(&self) -> Option<&[u8]> {
        match self {
            Borrowed::Data(d) => Some(d),
            Borrowed::Flush | Borrowed::Delimiter | Borrowed::ResponseEnd => None,
        }
    }
    /// Return this instance's [`as_slice()`][Borrowed::as_slice()] as [`BStr`].
    pub fn as_bstr(&self) -> Option<&BStr> {
        self.as_slice().map(Into::into)
    }
    /// Interpret this instance's [`as_slice()`][Borrowed::as_slice()] as [`Error`].
    ///
    /// This works for any data received in an error [channel][crate::Channel].
    ///
    /// Note that this creates an unchecked error using the slice verbatim, which is useful to [serialize it][Error::to_write()].
    /// See [`check_error()`][Borrowed::check_error()] for a version that assures the error information is in the expected format.
    pub fn to_error(&self) -> Option<Error<'_>> {
        self.as_slice().map(Error)
    }
    /// Check this instance's [`as_slice()`][Borrowed::as_slice()] is a valid [`Error`] and return it.
    ///
    /// This works for any data received in an error [channel][crate::Channel].
    pub fn check_error(&self) -> Option<Error<'_>> {
        self.as_slice().and_then(|data| {
            if data.len() >= ERR_PREFIX.len() && &data[..ERR_PREFIX.len()] == ERR_PREFIX {
                Some(Error(&data[ERR_PREFIX.len()..]))
            } else {
                None
            }
        })
    }
    /// Return this instance as text, with the trailing newline truncated if present.
    pub fn to_text(&self) -> Option<Text<'_>> {
        self.as_slice().map(Into::into)
    }

    /// Interpret the data in this [`slice`][Borrowed::as_slice()] as [`Band`] according to the given `kind` of channel.
    ///
    /// Note that this is only relevant in a side-band channel.
    /// See [`decode_band()`][Borrowed::decode_band()] in case `kind` is unknown.
    pub fn to_band(&self, kind: Channel) -> Option<Band<'_>> {
        self.as_slice().map(|d| match kind {
            Channel::Data => Band::Data(d),
            Channel::Progress => Band::Progress(d),
            Channel::Error => Band::Error(d),
        })
    }

    /// Decode the band of this [`slice`][Borrowed::as_slice()], or panic if it is not actually a side-band line.
    pub fn decode_band(&self) -> Result<Band<'_>, DecodeBandError> {
        let d = self.as_slice().ok_or(DecodeBandError::NonDataLine)?;
        Ok(match d[0] {
            1 => Band::Data(&d[1..]),
            2 => Band::Progress(&d[1..]),
            3 => Band::Error(&d[1..]),
            band => return Err(DecodeBandError::InvalidSideBand(band)),
        })
    }
}

use quick_error::quick_error;
quick_error! {
    /// The error used in [`decode_band()`][Borrowed::decode_band()].
    #[derive(Debug)]
    #[allow(missing_docs)]
    pub enum DecodeBandError {
        InvalidSideBand(band: u8) {
            display("attempt to decode a non-side channel line or input was malformed: {}", band)
        }
        NonDataLine {
            display("attempt to decode a non-data line into a side-channel band")
        }
    }
}

/// A packet line representing an Error in a side-band channel.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Error<'a>(pub &'a [u8]);

impl<'a> Error<'a> {
    /// Serialize this line as error to `out`.
    ///
    /// This includes a marker to allow decoding it outside of a side-band channel, returning the amount of bytes written.
    pub fn to_write(&self, out: impl io::Write) -> Result<usize, encode::Error> {
        encode::error_to_write(self.0, out)
    }
}

/// A packet line representing text, which may include a trailing newline.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Text<'a>(pub &'a [u8]);

impl<'a> From<&'a [u8]> for Text<'a> {
    fn from(d: &'a [u8]) -> Self {
        let d = if d[d.len() - 1] == b'\n' { &d[..d.len() - 1] } else { d };
        Text(d)
    }
}

impl<'a> Text<'a> {
    /// Return this instance's data.
    pub fn as_slice(&self) -> &[u8] {
        self.0
    }
    /// Return this instance's data as [`BStr`].
    pub fn as_bstr(&self) -> &BStr {
        self.0.into()
    }
    /// Serialize this instance to `out`, appending a newline if there is none, returning the amount of bytes written.
    pub fn to_write(&self, out: impl io::Write) -> Result<usize, encode::Error> {
        encode::text_to_write(self.0, out)
    }
}

/// A band in a side-band channel.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Band<'a> {
    /// A band carrying data.
    Data(&'a [u8]),
    /// A band carrying user readable progress information.
    Progress(&'a [u8]),
    /// A band carrying user readable errors.
    Error(&'a [u8]),
}

impl<'a> Band<'a> {
    /// Serialize this instance to `out`, returning the amount of bytes written.
    ///
    /// The data written to `out` can be decoded with [`Borrowed::decode_band()]`.
    pub fn to_write(&self, out: impl io::Write) -> Result<usize, encode::Error> {
        match self {
            Band::Data(d) => encode::band_to_write(Channel::Data, d, out),
            Band::Progress(d) => encode::band_to_write(Channel::Progress, d, out),
            Band::Error(d) => encode::band_to_write(Channel::Error, d, out),
        }
    }
}