use btoi::{btoi, btou};
use nom::{
branch::alt,
bytes::streaming::{tag, take_while_m_n},
character::streaming::crlf,
combinator::{map, map_res, value},
sequence::terminated,
AsChar, IResult, Parser,
};
use std::fmt;
mod ascii_parser;
pub use ascii_parser::{
parse_ascii_metadump_response, parse_ascii_response, parse_ascii_stats_response,
};
mod meta_parser;
pub use meta_parser::{
parse_meta_arithmetic_response, parse_meta_delete_response, parse_meta_get_response,
parse_meta_set_response,
};
#[derive(Clone, Debug, PartialEq)]
pub struct Value {
pub key: Vec<u8>,
pub cas: Option<u64>,
pub flags: Option<u32>,
pub data: Option<Vec<u8>>,
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct MetaValue {
pub key: Option<Vec<u8>>,
pub cas: Option<u64>,
pub flags: Option<u32>,
pub data: Option<Vec<u8>>,
pub status: Option<Status>,
pub hit_before: Option<bool>,
pub last_accessed: Option<u64>,
pub ttl_remaining: Option<i64>,
pub size: Option<u64>,
pub opaque_token: Option<Vec<u8>>,
pub is_stale: Option<bool>,
pub is_recache_winner: Option<bool>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Status {
Stored,
NotStored,
Deleted,
Touched,
NoOp,
Exists,
Value,
NotFound,
Error(ErrorKind),
}
#[derive(Clone, Debug, PartialEq)]
pub enum ErrorKind {
Generic(String),
NonexistentCommand,
Protocol(Option<String>),
Client(String),
Server(String),
KeyTooLong,
OpaqueTooLong,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Response {
Status(Status),
Data(Option<Vec<Value>>),
IncrDecr(u64),
}
#[derive(Clone, Debug, PartialEq)]
pub enum MetaResponse {
Status(Status),
Data(Option<Vec<MetaValue>>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum MetadumpResponse {
Busy(String),
BadClass(String),
Entry(KeyMetadata),
End,
}
#[derive(Clone, Debug, PartialEq)]
pub enum StatsResponse {
Entry(String, String),
End,
}
#[derive(Clone, Debug, PartialEq)]
pub struct KeyMetadata {
pub key: Vec<u8>,
pub expiration: i64,
pub last_accessed: u64,
pub cas: u64,
pub fetched: bool,
pub class_id: u32,
pub size: u32,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Stored => write!(f, "stored"),
Self::NotStored => write!(f, "not stored"),
Self::Deleted => write!(f, "deleted"),
Self::Touched => write!(f, "touched"),
Self::NoOp => write!(f, "no-op"),
Self::Exists => write!(f, "exists"),
Self::Value => write!(f, "value"),
Self::NotFound => write!(f, "not found"),
Self::Error(ek) => write!(f, "error: {}", ek),
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Generic(s) => write!(f, "generic: {}", s),
Self::NonexistentCommand => write!(f, "command does not exist"),
Self::Protocol(s) => match s {
Some(s) => write!(f, "protocol: {}", s),
None => write!(f, "protocol"),
},
Self::Client(s) => write!(f, "client: {}", s),
Self::Server(s) => write!(f, "server: {}", s),
Self::KeyTooLong => write!(f, "Key exceeds maximum allowed length of 250 characters"),
Self::OpaqueTooLong => {
write!(f, "Opaque exceeds maximum allowed length of 32 characters")
}
}
}
}
impl From<MetadumpResponse> for Status {
fn from(resp: MetadumpResponse) -> Self {
match resp {
MetadumpResponse::BadClass(s) => {
Status::Error(ErrorKind::Generic(format!("BADCLASS {}", s)))
}
MetadumpResponse::Busy(s) => Status::Error(ErrorKind::Generic(format!("BUSY {}", s))),
_ => unreachable!("Metadump Entry/End states should never be used as a Status!"),
}
}
}
pub(crate) fn parse_u64(buf: &[u8]) -> IResult<&[u8], u64> {
map_res(take_while_m_n(1, 20, |c: u8| c.is_dec_digit()), btou).parse(buf)
}
pub(crate) fn parse_i64(buf: &[u8]) -> IResult<&[u8], i64> {
map_res(take_while_m_n(1, 20, is_signed_digit), btoi).parse(buf)
}
pub(crate) fn parse_bool(buf: &[u8]) -> IResult<&[u8], bool> {
alt((value(true, tag(&b"yes"[..])), value(false, tag(&b"no"[..])))).parse(buf)
}
pub(crate) fn parse_incrdecr(buf: &[u8]) -> IResult<&[u8], Response> {
terminated(map(parse_u64, Response::IncrDecr), crlf).parse(buf)
}
pub(crate) fn is_key_char(chr: u8) -> bool {
chr > 32 && chr < 127
}
pub(crate) fn is_signed_digit(chr: u8) -> bool {
chr == 45 || (48..=57).contains(&chr)
}
pub(crate) fn parse_u32(buf: &[u8]) -> IResult<&[u8], u32> {
map_res(take_while_m_n(1, 10, |c: u8| c.is_dec_digit()), btou).parse(buf)
}