Skip to content

Commit e7a76c9

Browse files
authored
feat: adds badd command (#486)
Adds `badd` command to resp protocol implementation.
1 parent 777b275 commit e7a76c9

File tree

3 files changed

+165
-0
lines changed

3 files changed

+165
-0
lines changed

src/protocol/resp/src/request/badd.rs

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2022 Twitter, Inc.
2+
// Licensed under the Apache License, Version 2.0
3+
// http://www.apache.org/licenses/LICENSE-2.0
4+
5+
use super::*;
6+
use std::io::{Error, ErrorKind};
7+
use std::sync::Arc;
8+
9+
type ArcByteSlice = Arc<Box<[u8]>>;
10+
type ArcKeyValuePair = (ArcByteSlice, ArcByteSlice);
11+
12+
/// Represents the btree add command which was added to Twitter's internal
13+
/// version of redis32.
14+
/// format is: badd outer_key (inner_key value)+
15+
#[derive(Debug, PartialEq, Eq)]
16+
pub struct BAddRequest {
17+
outer_key: Arc<Box<[u8]>>,
18+
inner_key_value_pairs: Arc<Box<[ArcKeyValuePair]>>,
19+
}
20+
21+
impl BAddRequest {
22+
pub fn outer_key(&self) -> &[u8] {
23+
&self.outer_key
24+
}
25+
26+
pub fn inner_key_value_pairs(&self) -> Box<[(&[u8], &[u8])]> {
27+
self.inner_key_value_pairs
28+
.iter()
29+
.map(|(k, v)| (&***k, &***v))
30+
.collect::<Vec<(&[u8], &[u8])>>()
31+
.into_boxed_slice()
32+
}
33+
}
34+
35+
impl TryFrom<Message> for BAddRequest {
36+
type Error = Error;
37+
38+
fn try_from(other: Message) -> Result<Self, Error> {
39+
if let Message::Array(array) = other {
40+
if array.inner.is_none() {
41+
return Err(Error::new(ErrorKind::Other, "malformed command"));
42+
}
43+
44+
let mut array = array.inner.unwrap();
45+
46+
if array.len() < 4 {
47+
return Err(Error::new(ErrorKind::Other, "malformed command"));
48+
}
49+
50+
if array.len() % 2 == 1 {
51+
return Err(Error::new(ErrorKind::Other, "malformed command"));
52+
}
53+
54+
let outer_key = take_bulk_string(&mut array)?;
55+
if outer_key.is_empty() {
56+
return Err(Error::new(ErrorKind::Other, "malformed command"));
57+
}
58+
59+
//loop as long as we have at least 2 arguments after the command
60+
let mut pairs = Vec::with_capacity(array.len() / 2);
61+
while array.len() >= 3 {
62+
let inner_key = take_bulk_string(&mut array)?;
63+
if inner_key.is_empty() {
64+
return Err(Error::new(ErrorKind::Other, "malformed command"));
65+
}
66+
67+
let value = take_bulk_string(&mut array)?;
68+
if value.is_empty() {
69+
return Err(Error::new(ErrorKind::Other, "malformed command"));
70+
}
71+
72+
pairs.push((inner_key, value));
73+
}
74+
75+
Ok(Self {
76+
outer_key,
77+
inner_key_value_pairs: Arc::new(Box::<[ArcKeyValuePair]>::from(pairs)),
78+
})
79+
} else {
80+
Err(Error::new(ErrorKind::Other, "malformed command"))
81+
}
82+
}
83+
}
84+
85+
impl From<&BAddRequest> for Message {
86+
fn from(other: &BAddRequest) -> Message {
87+
let mut v = vec![
88+
Message::bulk_string(b"BADD"),
89+
Message::BulkString(BulkString::from(other.outer_key.clone())),
90+
];
91+
for kv in (*other.inner_key_value_pairs).iter() {
92+
v.push(Message::BulkString(BulkString::from(kv.0.clone())));
93+
v.push(Message::BulkString(BulkString::from(kv.1.clone())));
94+
}
95+
96+
Message::Array(Array { inner: Some(v) })
97+
}
98+
}
99+
100+
impl Compose for BAddRequest {
101+
fn compose(&self, buf: &mut dyn BufMut) -> usize {
102+
let message = Message::from(self);
103+
message.compose(buf)
104+
}
105+
}
106+
107+
#[cfg(test)]
108+
mod tests {
109+
use super::*;
110+
111+
#[test]
112+
fn parser() {
113+
let parser = RequestParser::new();
114+
115+
//1 key value pair
116+
if let Request::BAdd(request) = parser
117+
.parse(b"badd outer inner 42\r\n")
118+
.unwrap()
119+
.into_inner()
120+
{
121+
assert_eq!(request.outer_key(), b"outer");
122+
assert_eq!(request.inner_key_value_pairs.len(), 1);
123+
assert_eq!(request.inner_key_value_pairs()[0].0, b"inner");
124+
assert_eq!(request.inner_key_value_pairs()[0].1, b"42");
125+
} else {
126+
panic!("invalid parse result");
127+
}
128+
129+
//> 1 key value pairs
130+
if let Request::BAdd(request) = parser
131+
.parse(b"badd outer inner 42 inner2 7*6\r\n")
132+
.unwrap()
133+
.into_inner()
134+
{
135+
assert_eq!(request.outer_key(), b"outer");
136+
assert_eq!(request.inner_key_value_pairs.len(), 2);
137+
assert_eq!(request.inner_key_value_pairs()[0].0, b"inner");
138+
assert_eq!(request.inner_key_value_pairs()[0].1, b"42");
139+
assert_eq!(request.inner_key_value_pairs()[1].0, b"inner2");
140+
assert_eq!(request.inner_key_value_pairs()[1].1, b"7*6");
141+
} else {
142+
panic!("invalid parse result");
143+
}
144+
}
145+
}

src/protocol/resp/src/request/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ use protocol_common::ParseOk;
1010
use std::io::{Error, ErrorKind};
1111
use std::sync::Arc;
1212

13+
mod badd;
1314
mod get;
1415
mod set;
1516

17+
pub use badd::BAddRequest;
1618
pub use get::GetRequest;
1719
pub use set::SetRequest;
1820

@@ -87,6 +89,9 @@ impl Parse<Request> for RequestParser {
8789

8890
match &array[0] {
8991
Message::BulkString(c) => match c.inner.as_ref().map(|v| v.as_ref().as_ref()) {
92+
Some(b"badd") | Some(b"BADD") => {
93+
BAddRequest::try_from(message).map(Request::from)
94+
}
9095
Some(b"get") | Some(b"GET") => {
9196
GetRequest::try_from(message).map(Request::from)
9297
}
@@ -113,6 +118,7 @@ impl Parse<Request> for RequestParser {
113118
impl Compose for Request {
114119
fn compose(&self, buf: &mut dyn BufMut) -> usize {
115120
match self {
121+
Self::BAdd(r) => r.compose(buf),
116122
Self::Get(r) => r.compose(buf),
117123
Self::Set(r) => r.compose(buf),
118124
}
@@ -121,10 +127,17 @@ impl Compose for Request {
121127

122128
#[derive(Debug, PartialEq, Eq)]
123129
pub enum Request {
130+
BAdd(BAddRequest),
124131
Get(GetRequest),
125132
Set(SetRequest),
126133
}
127134

135+
impl From<BAddRequest> for Request {
136+
fn from(other: BAddRequest) -> Self {
137+
Self::BAdd(other)
138+
}
139+
}
140+
128141
impl From<GetRequest> for Request {
129142
fn from(other: GetRequest) -> Self {
130143
Self::Get(other)
@@ -139,6 +152,7 @@ impl From<SetRequest> for Request {
139152

140153
#[derive(Debug, PartialEq, Eq)]
141154
pub enum Command {
155+
BAdd,
142156
Get,
143157
Set,
144158
}
@@ -148,6 +162,7 @@ impl TryFrom<&[u8]> for Command {
148162

149163
fn try_from(other: &[u8]) -> Result<Self, ()> {
150164
match other {
165+
b"badd" | b"BADD" => Ok(Command::BAdd),
151166
b"get" | b"GET" => Ok(Command::Get),
152167
b"set" | b"SET" => Ok(Command::Set),
153168
_ => Err(()),

src/proxy/momento/src/frontend.rs

+5
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ pub(crate) async fn handle_resp_client(
102102
break;
103103
}
104104
}
105+
_ => {
106+
println!("bad request");
107+
let _ = socket.write_all(b"CLIENT_ERROR\r\n").await;
108+
break;
109+
}
105110
}
106111
buf.advance(consumed);
107112
}

0 commit comments

Comments
 (0)