use core::fmt;
use yo_common::{Code, Error};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ProtocolError {
InvalidMultibulkLength,
InvalidBulkLength,
ExpectedDollar(u8),
TooBigMbulkCount,
TooBigBulkCount,
TooBigInline,
UnbalancedQuotes,
UnknownType(u8),
TooDeep,
Unsupported(u8),
}
impl ProtocolError {
pub fn write_reply(&self, out: &mut Vec<u8>) {
out.extend_from_slice(b"-ERR Protocol error: ");
match *self {
ProtocolError::InvalidMultibulkLength => {
out.extend_from_slice(b"invalid multibulk length");
}
ProtocolError::InvalidBulkLength => out.extend_from_slice(b"invalid bulk length"),
ProtocolError::ExpectedDollar(got) => {
out.extend_from_slice(b"expected '$', got '");
out.push(if got == b'\r' || got == b'\n' {
b' '
} else {
got
});
out.push(b'\'');
}
ProtocolError::TooBigMbulkCount => {
out.extend_from_slice(b"too big mbulk count string");
}
ProtocolError::TooBigBulkCount => out.extend_from_slice(b"too big bulk count string"),
ProtocolError::TooBigInline => out.extend_from_slice(b"too big inline request"),
ProtocolError::UnbalancedQuotes => {
out.extend_from_slice(b"unbalanced quotes in request");
}
ProtocolError::UnknownType(got) => {
out.extend_from_slice(b"unknown type byte '");
out.push(if got == b'\r' || got == b'\n' {
b' '
} else {
got
});
out.push(b'\'');
}
ProtocolError::TooDeep => out.extend_from_slice(b"nesting too deep"),
ProtocolError::Unsupported(got) => {
out.extend_from_slice(b"unsupported type byte '");
out.push(if got == b'\r' || got == b'\n' {
b' '
} else {
got
});
out.push(b'\'');
}
}
out.extend_from_slice(b"\r\n");
}
}
impl fmt::Display for ProtocolError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut line = Vec::new();
self.write_reply(&mut line);
let body = &line[5..line.len() - 2];
f.write_str(&String::from_utf8_lossy(body))
}
}
impl core::error::Error for ProtocolError {}
impl From<ProtocolError> for Error {
fn from(e: ProtocolError) -> Error {
Error::new(Code::Invalid, e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn reply(e: ProtocolError) -> String {
let mut v = Vec::new();
e.write_reply(&mut v);
String::from_utf8(v).unwrap()
}
#[test]
fn the_messages_are_the_ones_redis_sends() {
assert_eq!(
reply(ProtocolError::InvalidMultibulkLength),
"-ERR Protocol error: invalid multibulk length\r\n"
);
assert_eq!(
reply(ProtocolError::InvalidBulkLength),
"-ERR Protocol error: invalid bulk length\r\n"
);
assert_eq!(
reply(ProtocolError::ExpectedDollar(b'x')),
"-ERR Protocol error: expected '$', got 'x'\r\n"
);
assert_eq!(
reply(ProtocolError::TooBigMbulkCount),
"-ERR Protocol error: too big mbulk count string\r\n"
);
assert_eq!(
reply(ProtocolError::TooBigBulkCount),
"-ERR Protocol error: too big bulk count string\r\n"
);
assert_eq!(
reply(ProtocolError::TooBigInline),
"-ERR Protocol error: too big inline request\r\n"
);
assert_eq!(
reply(ProtocolError::UnbalancedQuotes),
"-ERR Protocol error: unbalanced quotes in request\r\n"
);
}
#[test]
fn a_newline_in_the_offending_byte_does_not_end_the_line() {
let r = reply(ProtocolError::ExpectedDollar(b'\n'));
assert_eq!(r, "-ERR Protocol error: expected '$', got ' '\r\n");
assert_eq!(r.matches("\r\n").count(), 1);
}
#[test]
fn it_carries_into_the_typed_api_as_an_invalid_argument() {
let e: Error = ProtocolError::InvalidBulkLength.into();
assert_eq!(e.code(), Code::Invalid);
assert_eq!(e.message(), "Protocol error: invalid bulk length");
}
}