async_memcached/parser/
mod.rs

1use btoi::{btoi, btou};
2use nom::{
3    branch::alt,
4    bytes::streaming::{tag, take_while_m_n},
5    character::{is_digit, streaming::crlf},
6    combinator::{map, map_res, value},
7    sequence::terminated,
8    IResult,
9};
10use std::fmt;
11
12mod ascii_parser;
13pub use ascii_parser::{
14    parse_ascii_metadump_response, parse_ascii_response, parse_ascii_stats_response,
15};
16
17mod meta_parser;
18pub use meta_parser::{
19    parse_meta_arithmetic_response, parse_meta_delete_response, parse_meta_get_response,
20    parse_meta_set_response,
21};
22
23/// A value from memcached generated by an ASCII protocol response.
24#[derive(Clone, Debug, PartialEq)]
25pub struct Value {
26    /// The key.
27    pub key: Vec<u8>,
28    /// CAS identifier.
29    pub cas: Option<u64>,
30    /// Flags for this key.
31    /// Defaults to 0.
32    pub flags: Option<u32>,
33    /// Data for this key.
34    pub data: Option<Vec<u8>>,
35}
36
37/// A value from memcached generated by a meta protocol response.
38#[derive(Clone, Debug, PartialEq, Default)]
39pub struct MetaValue {
40    /// The key.
41    pub key: Option<Vec<u8>>,
42    /// CAS value (c flag)
43    pub cas: Option<u64>,
44    /// Client bitflags (f flag)
45    pub flags: Option<u32>,
46    /// Data for this key.
47    pub data: Option<Vec<u8>>,
48    /// Status of the operation for set/add/replace commands
49    pub status: Option<Status>,
50    /// Whether the item has been accessed before (h flag)
51    pub hit_before: Option<bool>,
52    /// Last access time in seconds since the epoch (l flag)
53    pub last_accessed: Option<u64>,
54    /// Remaining TTL in seconds, or -1 for unlimited (t flag)
55    pub ttl_remaining: Option<i64>,
56    /// Value size (s flag)
57    pub size: Option<u64>,
58    /// opaque value, consumes a token and copies back with response (O flag)
59    pub opaque_token: Option<Vec<u8>>,
60    /// Staleness indicator (X flag)
61    pub is_stale: Option<bool>,
62    /// Win/lose for recache (W/Z flag)
63    pub is_recache_winner: Option<bool>,
64}
65
66/// Status of a memcached operation.
67#[derive(Clone, Debug, PartialEq)]
68pub enum Status {
69    /// The value was stored.
70    Stored,
71    /// The value was not stored.
72    NotStored,
73    /// The key was deleted.
74    Deleted,
75    /// The key was touched.
76    Touched,
77    /// Quiet mode no-op.
78    NoOp,
79    /// The key already exists.
80    Exists,
81    /// The key already exists and value was requested in the meta protocol.
82    Value,
83    /// The key was not found.
84    NotFound,
85    /// An error occurred for the given operation.
86    Error(ErrorKind),
87}
88
89/// Errors related to a memcached operation.
90#[derive(Clone, Debug, PartialEq)]
91pub enum ErrorKind {
92    /// General error that may or may not have come from either the server or this crate.
93    Generic(String),
94    /// The command sent by the client does not exist.
95    NonexistentCommand,
96    /// Protocol-level error i.e. an invalid response from memcached for the given operation.
97    Protocol(Option<String>),
98    /// An error from memcached related to CLIENT_ERROR.
99    Client(String),
100    /// An error from memcached related to SERVER_ERROR.
101    Server(String),
102    /// An error from memcached related to a key that exceeds maximum allowed length (250 bytes).
103    KeyTooLong,
104    /// An error from memcached related to a key that exceeds maximum allowed length (32 bytes).
105    OpaqueTooLong,
106}
107
108/// Response to a memcached operation.
109#[derive(Clone, Debug, PartialEq)]
110pub enum Response {
111    /// The status of a given operation, which may or may not have succeeded.
112    Status(Status),
113    /// Data response, which is only returned for reads.
114    Data(Option<Vec<Value>>),
115    /// Resulting value of a key after an increment/decrement operation.
116    IncrDecr(u64),
117}
118
119/// Response to a memcached meta protocol operation.
120#[derive(Clone, Debug, PartialEq)]
121pub enum MetaResponse {
122    /// The status of a given operation, which may or may not have succeeded.
123    Status(Status),
124    /// Data response, which is only returned for when requested via the 'v' meta flag.
125    Data(Option<Vec<MetaValue>>),
126}
127
128/// Metadump response.
129#[derive(Clone, Debug, PartialEq)]
130pub enum MetadumpResponse {
131    /// The server is busy running another LRU crawler operation.
132    Busy(String),
133    /// An invalid class ID was specified for the metadump.
134    BadClass(String),
135    /// A single key entry within the overall metadump operation.
136    Entry(KeyMetadata),
137    /// End of the metadump.
138    End,
139}
140
141/// Stats response.
142#[derive(Clone, Debug, PartialEq)]
143pub enum StatsResponse {
144    /// A stats entry, represented by a key and value.
145    Entry(String, String),
146    /// End of stats output.
147    End,
148}
149
150/// Metadata for a given key in a metadump operation.
151#[derive(Clone, Debug, PartialEq)]
152pub struct KeyMetadata {
153    /// The key.
154    pub key: Vec<u8>,
155    /// Expiration time of this key, as a Unix timestamp.
156    pub expiration: i64,
157    /// Last time this key was accessed, in seconds.
158    pub last_accessed: u64,
159    /// CAS identifier.
160    pub cas: u64,
161    /// Whether or not this key has ever been fetched.
162    pub fetched: bool,
163    /// Slab class ID.
164    pub class_id: u32,
165    /// Size, in bytes.
166    pub size: u32,
167}
168
169impl fmt::Display for Status {
170    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171        match self {
172            Self::Stored => write!(f, "stored"),
173            Self::NotStored => write!(f, "not stored"),
174            Self::Deleted => write!(f, "deleted"),
175            Self::Touched => write!(f, "touched"),
176            Self::NoOp => write!(f, "no-op"),
177            Self::Exists => write!(f, "exists"),
178            Self::Value => write!(f, "value"),
179            Self::NotFound => write!(f, "not found"),
180            Self::Error(ek) => write!(f, "error: {}", ek),
181        }
182    }
183}
184
185impl fmt::Display for ErrorKind {
186    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187        match self {
188            Self::Generic(s) => write!(f, "generic: {}", s),
189            Self::NonexistentCommand => write!(f, "command does not exist"),
190            Self::Protocol(s) => match s {
191                Some(s) => write!(f, "protocol: {}", s),
192                None => write!(f, "protocol"),
193            },
194            Self::Client(s) => write!(f, "client: {}", s),
195            Self::Server(s) => write!(f, "server: {}", s),
196            Self::KeyTooLong => write!(f, "Key exceeds maximum allowed length of 250 characters"),
197            Self::OpaqueTooLong => {
198                write!(f, "Opaque exceeds maximum allowed length of 32 characters")
199            }
200        }
201    }
202}
203
204impl From<MetadumpResponse> for Status {
205    fn from(resp: MetadumpResponse) -> Self {
206        match resp {
207            MetadumpResponse::BadClass(s) => {
208                Status::Error(ErrorKind::Generic(format!("BADCLASS {}", s)))
209            }
210            MetadumpResponse::Busy(s) => Status::Error(ErrorKind::Generic(format!("BUSY {}", s))),
211            _ => unreachable!("Metadump Entry/End states should never be used as a Status!"),
212        }
213    }
214}
215
216// shared parsing functions
217pub(crate) fn parse_u64(buf: &[u8]) -> IResult<&[u8], u64> {
218    map_res(take_while_m_n(1, 20, is_digit), btou)(buf)
219}
220
221pub(crate) fn parse_i64(buf: &[u8]) -> IResult<&[u8], i64> {
222    map_res(take_while_m_n(1, 20, is_signed_digit), btoi)(buf)
223}
224
225pub(crate) fn parse_bool(buf: &[u8]) -> IResult<&[u8], bool> {
226    alt((value(true, tag(b"yes")), value(false, tag(b"no"))))(buf)
227}
228
229pub(crate) fn parse_incrdecr(buf: &[u8]) -> IResult<&[u8], Response> {
230    terminated(map(parse_u64, Response::IncrDecr), crlf)(buf)
231}
232
233pub(crate) fn is_key_char(chr: u8) -> bool {
234    chr > 32 && chr < 127
235}
236
237pub(crate) fn is_signed_digit(chr: u8) -> bool {
238    chr == 45 || (48..=57).contains(&chr)
239}
240
241pub(crate) fn parse_u32(buf: &[u8]) -> IResult<&[u8], u32> {
242    map_res(take_while_m_n(1, 10, is_digit), btou)(buf)
243}