Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 88 additions & 2 deletions src/parser/meta_parser.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use nom::{
branch::alt,
bytes::streaming::{tag, take, take_while1},
bytes::streaming::{tag, take, take_until, take_while1},
character::streaming::{crlf, space1},
combinator::{map, opt, value},
combinator::{map, map_res, opt, value},
error::ErrorKind::Fail,
multi::many0,
sequence::{preceded, terminated},
IResult, Parser,
};

Expand All @@ -13,12 +14,32 @@ use std::num::NonZero;
use super::{parse_u32, ErrorKind, MetaResponse, MetaValue, Status};
use crate::Error;

fn parse_meta_error(buf: &[u8]) -> IResult<&[u8], MetaResponse> {
let parser = terminated(
alt((
value(ErrorKind::NonexistentCommand, tag(&b"ERROR"[..])),
map_res(
preceded(tag(&b"CLIENT_ERROR "[..]), take_until("\r\n")),
|s| std::str::from_utf8(s).map(|s| ErrorKind::Client(s.to_string())),
),
map_res(
preceded(tag(&b"SERVER_ERROR "[..]), take_until("\r\n")),
|s| std::str::from_utf8(s).map(|s| ErrorKind::Server(s.to_string())),
),
)),
crlf,
);

map(parser, |e| MetaResponse::Status(Status::Error(e))).parse(buf)
}

pub fn parse_meta_get_status(buf: &[u8]) -> IResult<&[u8], MetaResponse> {
alt((
value(MetaResponse::Status(Status::Value), tag(&b"VA "[..])),
value(MetaResponse::Status(Status::Exists), tag(&b"HD"[..])),
value(MetaResponse::Status(Status::NotFound), tag(&b"EN"[..])),
value(MetaResponse::Status(Status::NoOp), tag(&b"MN\r\n"[..])),
parse_meta_error,
))
.parse(buf)
}
Expand All @@ -30,6 +51,7 @@ pub fn parse_meta_set_status(buf: &[u8]) -> IResult<&[u8], MetaResponse> {
value(MetaResponse::Status(Status::Exists), tag(&b"EX"[..])),
value(MetaResponse::Status(Status::NotFound), tag(&b"NF"[..])),
value(MetaResponse::Status(Status::NoOp), tag(&b"MN\r\n"[..])),
parse_meta_error,
))
.parse(buf)
}
Expand All @@ -40,6 +62,7 @@ pub fn parse_meta_delete_status(buf: &[u8]) -> IResult<&[u8], MetaResponse> {
value(MetaResponse::Status(Status::NotFound), tag(&b"NF"[..])),
value(MetaResponse::Status(Status::Exists), tag(&b"EX"[..])),
value(MetaResponse::Status(Status::NoOp), tag(&b"MN\r\n"[..])),
parse_meta_error,
))
.parse(buf)
}
Expand All @@ -52,6 +75,7 @@ pub fn parse_meta_arithmetic_status(buf: &[u8]) -> IResult<&[u8], MetaResponse>
value(MetaResponse::Status(Status::NotStored), tag(&b"NS"[..])),
value(MetaResponse::Status(Status::Exists), tag(&b"EX"[..])),
value(MetaResponse::Status(Status::NoOp), tag(&b"MN\r\n"[..])),
parse_meta_error,
))
.parse(buf)
}
Expand Down Expand Up @@ -196,6 +220,8 @@ fn parse_meta_get_data_value(buf: &[u8]) -> IResult<&[u8], MetaResponse> {
}
// match arm for "MN\r\n" response
MetaResponse::Status(Status::NoOp) => Ok((input, MetaResponse::Status(Status::NoOp))),
// match arm for standard ASCII error responses: ERROR, CLIENT_ERROR, SERVER_ERROR
MetaResponse::Status(Status::Error(_)) => Ok((input, status)),
_ => {
// unexpected response code, should never happen, bail
Err(nom::Err::Error(nom::error::Error::new(
Expand Down Expand Up @@ -234,6 +260,8 @@ fn parse_meta_set_data_value(buf: &[u8]) -> IResult<&[u8], MetaResponse> {
match status {
// match arm for "MN\r\n" response
MetaResponse::Status(Status::NoOp) => Ok((input, MetaResponse::Status(Status::NoOp))),
// match arm for standard ASCII error responses: ERROR, CLIENT_ERROR, SERVER_ERROR
MetaResponse::Status(Status::Error(_)) => Ok((input, status)),
// match arm for "HD", "NS", "EX" & "NF" responses
MetaResponse::Status(s) => process_meta_response_without_data_payload(input, s),
_ => Err(nom::Err::Error(nom::error::Error::new(
Expand Down Expand Up @@ -271,6 +299,8 @@ fn parse_meta_delete_data_value(buf: &[u8]) -> IResult<&[u8], MetaResponse> {
match status {
// match arm for "MN\r\n" response
MetaResponse::Status(Status::NoOp) => Ok((input, MetaResponse::Status(Status::NoOp))),
// match arm for standard ASCII error responses: ERROR, CLIENT_ERROR, SERVER_ERROR
MetaResponse::Status(Status::Error(_)) => Ok((input, status)),
// match arm for "HD", "NF" & "EX" responses
MetaResponse::Status(s) => process_meta_response_without_data_payload(input, s),
_ => Err(nom::Err::Error(nom::error::Error::new(
Expand Down Expand Up @@ -299,6 +329,8 @@ fn parse_meta_arithmetic_data_value(buf: &[u8]) -> IResult<&[u8], MetaResponse>
}
// match arm for "MN\r\n" response
MetaResponse::Status(Status::NoOp) => Ok((input, MetaResponse::Status(Status::NoOp))),
// match arm for standard ASCII error responses: ERROR, CLIENT_ERROR, SERVER_ERROR
MetaResponse::Status(Status::Error(_)) => Ok((input, status)),
// match arm for "HD", "NF" & "EX" responses
MetaResponse::Status(s) => process_meta_response_without_data_payload(input, s),
_ => Err(nom::Err::Error(nom::error::Error::new(
Expand Down Expand Up @@ -988,4 +1020,58 @@ mod tests {
_ => panic!("Expected Response::Data, got something else"),
}
}

#[test]
fn test_parse_meta_get_response_handles_error() {
let response = parse_meta_get_response(b"ERROR\r\n").unwrap().unwrap();

assert_eq!(response.0, 7);
assert_eq!(
response.1,
MetaResponse::Status(Status::Error(ErrorKind::NonexistentCommand))
);
}

#[test]
fn test_parse_meta_set_response_handles_client_error() {
let response = parse_meta_set_response(b"CLIENT_ERROR bad command line format\r\n")
.unwrap()
.unwrap();

assert_eq!(response.0, 38);
assert_eq!(
response.1,
MetaResponse::Status(Status::Error(ErrorKind::Client(
"bad command line format".to_string()
)))
);
}

#[test]
fn test_parse_meta_delete_response_handles_server_error() {
let response = parse_meta_delete_response(b"SERVER_ERROR out of memory\r\n")
.unwrap()
.unwrap();

assert_eq!(response.0, 28);
assert_eq!(
response.1,
MetaResponse::Status(Status::Error(ErrorKind::Server(
"out of memory".to_string()
)))
);
}

#[test]
fn test_parse_meta_arithmetic_response_handles_client_error() {
let response = parse_meta_arithmetic_response(b"CLIENT_ERROR bad flag\r\n")
.unwrap()
.unwrap();

assert_eq!(response.0, 23);
assert_eq!(
response.1,
MetaResponse::Status(Status::Error(ErrorKind::Client("bad flag".to_string())))
);
}
}
56 changes: 50 additions & 6 deletions src/proto/meta_protocol.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{AsMemcachedValue, Client, Error, Status};
use crate::{AsMemcachedValue, Client, Error, ErrorKind, Status};

use crate::parser::{
parse_meta_arithmetic_response, parse_meta_delete_response, parse_meta_get_response,
Expand All @@ -10,6 +10,25 @@ use std::future::Future;

use tokio::io::AsyncWriteExt;

type MetaResponseParser = fn(&[u8]) -> Result<Option<(usize, MetaResponse)>, ErrorKind>;

async fn drain_quiet_noop_response(
client: &mut Client,
parser: MetaResponseParser,
) -> Result<(), Error> {
match client.drive_receive(parser).await? {
MetaResponse::Status(Status::NoOp) => Ok(()),
MetaResponse::Status(status) => Err(Status::Error(ErrorKind::Protocol(Some(format!(
"Expected quiet-mode no-op response, got status {status}"
))))
.into()),
MetaResponse::Data(_) => Err(Status::Error(ErrorKind::Protocol(Some(
"Expected quiet-mode no-op response, got data response".to_string(),
)))
.into()),
}
}

/// Trait defining Meta protocol-specific methods for the Client.
pub trait MetaProtocol {
/// Gets the given key with additional metadata.
Expand Down Expand Up @@ -174,7 +193,12 @@ impl MetaProtocol for Client {

self.conn.flush().await?;

match self.drive_receive(parse_meta_get_response).await? {
let response = self.drive_receive(parse_meta_get_response).await?;
if is_quiet && !matches!(response, MetaResponse::Status(Status::NoOp)) {
drain_quiet_noop_response(self, parse_meta_get_response).await?;
}

match response {
MetaResponse::Status(Status::NotFound) => Ok(None),
MetaResponse::Status(Status::NoOp) => Ok(None),
MetaResponse::Status(s) => Err(s.into()),
Expand Down Expand Up @@ -232,7 +256,12 @@ impl MetaProtocol for Client {

self.conn.flush().await?;

match self.drive_receive(parse_meta_set_response).await? {
let response = self.drive_receive(parse_meta_set_response).await?;
if is_quiet && !matches!(response, MetaResponse::Status(Status::NoOp)) {
drain_quiet_noop_response(self, parse_meta_set_response).await?;
}

match response {
MetaResponse::Status(Status::Stored) => Ok(None),
MetaResponse::Status(Status::NoOp) => Ok(None),
MetaResponse::Status(s) => Err(s.into()),
Expand Down Expand Up @@ -269,7 +298,12 @@ impl MetaProtocol for Client {

self.conn.flush().await?;

match self.drive_receive(parse_meta_delete_response).await? {
let response = self.drive_receive(parse_meta_delete_response).await?;
if is_quiet && !matches!(response, MetaResponse::Status(Status::NoOp)) {
drain_quiet_noop_response(self, parse_meta_delete_response).await?;
}

match response {
MetaResponse::Status(Status::Deleted) => Ok(None),
MetaResponse::Status(Status::Exists) => Err(Error::Protocol(Status::Exists)),
MetaResponse::Status(Status::NoOp) => Ok(None),
Expand Down Expand Up @@ -331,7 +365,12 @@ impl MetaProtocol for Client {

self.conn.flush().await?;

match self.drive_receive(parse_meta_arithmetic_response).await? {
let response = self.drive_receive(parse_meta_arithmetic_response).await?;
if is_quiet && !matches!(response, MetaResponse::Status(Status::NoOp)) {
drain_quiet_noop_response(self, parse_meta_arithmetic_response).await?;
}

match response {
MetaResponse::Status(Status::Stored) => Ok(None),
MetaResponse::Status(Status::NoOp) => Ok(None),
MetaResponse::Status(s) => Err(s.into()),
Expand Down Expand Up @@ -392,7 +431,12 @@ impl MetaProtocol for Client {

self.conn.flush().await?;

match self.drive_receive(parse_meta_arithmetic_response).await? {
let response = self.drive_receive(parse_meta_arithmetic_response).await?;
if is_quiet && !matches!(response, MetaResponse::Status(Status::NoOp)) {
drain_quiet_noop_response(self, parse_meta_arithmetic_response).await?;
}

match response {
MetaResponse::Status(Status::Stored) => Ok(None),
MetaResponse::Status(Status::NoOp) => Ok(None),
MetaResponse::Status(s) => Err(s.into()),
Expand Down
Loading
Loading