Skip to content

Commit

Permalink
feature: use winnow to parse resp protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
rcrwhyg committed Nov 14, 2024
1 parent 9b365c6 commit 404e825
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 4 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"hgetall",
"hmap",
"hset",
"raddr"
"raddr",
"respv"
]
}
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ tokio-stream = "0.1.16"
tokio-util = { version = "0.7.12", features = ["codec"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
winnow = { version = "0.6.20", features = ["simd"] }
4 changes: 2 additions & 2 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ feature-depth = 1
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory databases are cloned/fetched into
#db-path = "$CARGO_HOME/advisory-dbs"
db-path = "$CARGO_HOME/advisory-dbs"
# The url(s) of the advisory databases to use
#db-urls = ["https://github.com/rustsec/advisory-db"]
db-urls = ["https://github.com/rustsec/advisory-db"]
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod backend;
mod resp;
mod respv2;

pub mod cmd;
pub mod network;

pub use backend::*;
pub use resp::*;
pub use respv2::*;
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use tracing::{info, warn};

#[tokio::main]
async fn main() -> Result<()> {
std::env::set_var("RUST_LOG", "info");
tracing_subscriber::fmt::init();

let addr = "0.0.0.0:6379";
Expand Down
2 changes: 1 addition & 1 deletion src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tracing::info;

use crate::{
cmd::{Command, CommandExecutor},
Backend, RespDecode, RespEncode, RespError, RespFrame,
Backend, RespDecodeV2, RespEncode, RespError, RespFrame,
};

#[derive(Debug)]
Expand Down
6 changes: 6 additions & 0 deletions src/resp/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ impl Deref for RespArray {
}
}

impl From<Vec<RespFrame>> for RespArray {
fn from(v: Vec<RespFrame>) -> Self {
RespArray(v)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
6 changes: 6 additions & 0 deletions src/resp/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ impl DerefMut for RespMap {
}
}

impl From<BTreeMap<String, RespFrame>> for RespMap {
fn from(map: BTreeMap<String, RespFrame>) -> Self {
RespMap(map)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
197 changes: 197 additions & 0 deletions src/respv2/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
use bytes::BytesMut;

use crate::{RespError, RespFrame};

use parser::{parse_frame, parse_frame_length};

mod parser;

pub trait RespDecodeV2: Sized {
fn decode(buf: &mut BytesMut) -> Result<Self, RespError>;
fn expect_length(buf: &[u8]) -> Result<usize, RespError>;
}

impl RespDecodeV2 for RespFrame {
fn decode(buf: &mut BytesMut) -> Result<Self, RespError> {
let len = Self::expect_length(buf)?;
let data = buf.split_to(len);

parse_frame(&mut data.as_ref()).map_err(|e| RespError::InvalidFrame(e.to_string()))
}

fn expect_length(buf: &[u8]) -> Result<usize, RespError> {
parse_frame_length(buf)
}
}

#[cfg(test)]
mod test {
use std::collections::BTreeMap;

use super::*;
use crate::{RespFrame, RespNullArray, RespNullBulkString};
use anyhow::Result;

#[test]
fn respv2_simple_string_length_should_work() -> Result<()> {
let buf = b"+OK\r\n";
let len = RespFrame::expect_length(buf)?;
assert_eq!(len, buf.len());
Ok(())
}

#[test]
fn respv2_simple_string_length_bad_should_fail() -> Result<()> {
let buf = b"+OK";
let ret = RespFrame::expect_length(buf);
assert_eq!(ret.unwrap_err(), RespError::NotComplete);
Ok(())
}

#[test]
fn respv2_simple_string_should_work() -> Result<()> {
let mut buf = BytesMut::from("+OK\r\n");
let frame = RespFrame::decode(&mut buf)?;
assert_eq!(frame, RespFrame::SimpleString("OK".into()));
Ok(())
}

#[test]
fn respv2_simple_error_length_should_work() -> Result<()> {
let buf = b"-ERR\r\n";
let len = RespFrame::expect_length(buf)?;
assert_eq!(len, buf.len());
Ok(())
}

#[test]
fn respv2_simple_error_should_work() -> Result<()> {
let mut buf = BytesMut::from("-ERR\r\n");
let frame = RespFrame::decode(&mut buf)?;
assert_eq!(frame, RespFrame::Error("ERR".into()));
Ok(())
}

#[test]
fn respv2_integer_length_should_work() -> Result<()> {
let buf = b":1000\r\n";
let len = RespFrame::expect_length(buf)?;
assert_eq!(len, buf.len());
Ok(())
}

#[test]
fn respv2_integer_should_work() -> Result<()> {
let mut buf = BytesMut::from(":1000\r\n");
let frame = RespFrame::decode(&mut buf)?;
assert_eq!(frame, RespFrame::Integer(1000));
Ok(())
}

#[test]
fn respv2_bulk_string_length_should_work() -> Result<()> {
let buf = b"$5\r\nhello\r\n";
let len = RespFrame::expect_length(buf)?;
assert_eq!(len, buf.len());
Ok(())
}

#[test]
fn respv2_bulk_string_should_work() -> Result<()> {
let mut buf = BytesMut::from("$5\r\nhello\r\n");
let frame = RespFrame::decode(&mut buf)?;
assert_eq!(frame, RespFrame::BulkString("hello".into()));
Ok(())
}

#[test]
fn respv2_null_bulk_string_length_should_work() -> Result<()> {
let buf = b"$-1\r\n";
let len = RespFrame::expect_length(buf)?;
assert_eq!(len, buf.len());
Ok(())
}

#[test]
fn respv2_null_bulk_string_should_work() -> Result<()> {
let mut buf = BytesMut::from("$-1\r\n");
let frame = RespFrame::decode(&mut buf)?;
assert_eq!(frame, RespFrame::NullBulkString(RespNullBulkString));
Ok(())
}

#[test]
fn respv2_array_length_should_work() -> Result<()> {
let buf = b"*2\r\n$3\r\nset\r\n$5\r\nhello\r\n";
let len = RespFrame::expect_length(buf)?;
assert_eq!(len, buf.len());
Ok(())
}

#[test]
fn respv2_array_should_work() -> Result<()> {
let mut buf = BytesMut::from("*2\r\n$3\r\nset\r\n$5\r\nhello\r\n");
let frame = RespFrame::decode(&mut buf)?;
assert_eq!(
frame,
RespFrame::Array(
vec![
RespFrame::BulkString("set".into()),
RespFrame::BulkString("hello".into())
]
.into()
)
);
Ok(())
}

#[test]
fn respv2_null_array_length_should_work() -> Result<()> {
let buf = b"*-1\r\n";
let len = RespFrame::expect_length(buf)?;
assert_eq!(len, buf.len());
Ok(())
}

#[test]
fn respv2_null_array_should_work() -> Result<()> {
let mut buf = BytesMut::from("*-1\r\n");
let frame = RespFrame::decode(&mut buf)?;
assert_eq!(frame, RespFrame::NullArray(RespNullArray));
Ok(())
}

#[test]
fn respv2_map_length_should_work() -> Result<()> {
let buf = b"%1\r\n+OK\r\n-ERR\r\n";
let len = RespFrame::expect_length(buf).unwrap();
assert_eq!(len, buf.len());
Ok(())
}

#[test]
fn respv2_map_should_work() -> Result<()> {
let mut buf = BytesMut::from("%1\r\n+OK\r\n-ERR\r\n");
let frame = RespFrame::decode(&mut buf).unwrap();
let items: BTreeMap<String, RespFrame> =
[("OK".to_string(), RespFrame::Error("ERR".into()))]
.into_iter()
.collect();
assert_eq!(frame, RespFrame::Map(items.into()));
Ok(())
}

#[test]
fn respv2_map_with_real_data_should_work() -> Result<()> {
let mut buf = BytesMut::from("%2\r\n+hello\r\n$5\r\nworld\r\n+foo\r\n$3\r\nbar\r\n");
let frame = RespFrame::decode(&mut buf).unwrap();
let items: BTreeMap<String, RespFrame> = [
("hello".to_string(), RespFrame::BulkString("world".into())),
("foo".to_string(), RespFrame::BulkString("bar".into())),
]
.into_iter()
.collect();
assert_eq!(frame, RespFrame::Map(items.into()));
Ok(())
}
}
Loading

0 comments on commit 404e825

Please # to comment.