Skip to content

Commit

Permalink
impl for protocol and parser, copying code from mini-redis
Browse files Browse the repository at this point in the history
  • Loading branch information
NickNYU committed Jul 26, 2024
1 parent 56b4ea9 commit 71338f2
Show file tree
Hide file tree
Showing 13 changed files with 1,010 additions and 26 deletions.
96 changes: 96 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions resp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bytes = "1"
atoi = "2.0.0"
30 changes: 18 additions & 12 deletions resp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
pub mod protocol;
pub mod parse;

#[cfg(test)]
mod tests {
use super::*;
/// Error returned by most functions.
///
/// When writing a real application, one might want to consider a specialized
/// error handling crate or defining an error type as an `enum` of causes.
/// However, for our example, using a boxed `std::error::Error` is sufficient.
///
/// For performance reasons, boxing is avoided in any hot path. For example, in
/// `parse`, a custom error `enum` is defined. This is because the error is hit
/// and handled during normal execution when a partial frame is received on a
/// socket. `std::error::Error` is implemented for `parse::Error` which allows
/// it to be converted to `Box<dyn std::error::Error>`.
pub type Error = Box<dyn std::error::Error + Send + Sync>;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
/// A specialized `Result` type for rudis operations.
///
/// This is defined as a convenience.
pub type Result<T> = std::result::Result<T, Error>;
148 changes: 148 additions & 0 deletions resp/src/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use crate::protocol::Protocol;
use bytes::Bytes;
use std::{fmt, str, vec};

/// Utility for parsing a command
///
/// Commands are represented as array Protocols. Each entry in the Protocol is a
/// "token". A `Parse` is initialized with the array Protocol and provides a
/// cursor-like API. Each command struct includes a `parse_Protocol` method that
/// uses a `Parse` to extract its fields.
#[derive(Debug)]
pub struct Parser {
/// Array Protocol iterator.
parts: vec::IntoIter<Protocol>,
}

/// Error encountered while parsing a Protocol.
///
/// Only `EndOfStream` errors are handled at runtime. All other errors result in
/// the connection being terminated.
#[derive(Debug)]
pub enum ParseError {
/// Attempting to extract a value failed due to the Protocol being fully
/// consumed.
EndOfStream,

/// All other errors
Other(crate::Error),
}

impl Parser {
/// Create a new `Parse` to parse the contents of `protocol`.
///
/// Returns `Err` if `protocol` is not an array protocol.
pub fn new(protocol: Protocol) -> Result<Parser, ParseError> {
let array = match protocol {
Protocol::Array(array) => array,
Protocol => return Err(format!("protocol error; expected array, got {:?}", Protocol).into()),
};

Ok(Parser {
parts: array.into_iter(),
})
}

/// Return the next entry. Array Protocols are arrays of Protocols, so the next
/// entry is a Protocol.
fn next(&mut self) -> Result<Protocol, ParseError> {
self.parts.next().ok_or(ParseError::EndOfStream)
}

/// Return the next entry as a string.
///
/// If the next entry cannot be represented as a String, then an error is returned.
pub fn next_string(&mut self) -> Result<String, ParseError> {
match self.next()? {
// Both `Simple` and `Bulk` representation may be strings. Strings
// are parsed to UTF-8.
//
// While errors are stored as strings, they are considered separate
// types.
Protocol::Simple(s) => Ok(s),
Protocol::Bulk(data) => str::from_utf8(&data[..])
.map(|s| s.to_string())
.map_err(|_| "protocol error; invalid string".into()),
Protocol => Err(format!(
"protocol error; expected simple Protocol or bulk Protocol, got {:?}",
Protocol
)
.into()),
}
}

/// Return the next entry as raw bytes.
///
/// If the next entry cannot be represented as raw bytes, an error is
/// returned.
pub fn next_bytes(&mut self) -> Result<Bytes, ParseError> {
match self.next()? {
// Both `Simple` and `Bulk` representation may be raw bytes.
//
// Although errors are stored as strings and could be represented as
// raw bytes, they are considered separate types.
Protocol::Simple(s) => Ok(Bytes::from(s.into_bytes())),
Protocol::Bulk(data) => Ok(data),
Protocol => Err(format!(
"protocol error; expected simple Protocol or bulk Protocol, got {:?}",
Protocol
)
.into()),
}
}

/// Return the next entry as an integer.
///
/// This includes `Simple`, `Bulk`, and `Integer` Protocol types. `Simple` and
/// `Bulk` Protocol types are parsed.
///
/// If the next entry cannot be represented as an integer, then an error is
/// returned.
pub(crate) fn next_int(&mut self) -> Result<u64, ParseError> {
use atoi::atoi;

const MSG: &str = "protocol error; invalid number";

match self.next()? {
// An integer Protocol type is already stored as an integer.
Protocol::Integer(v) => Ok(v),
// Simple and bulk Protocols must be parsed as integers. If the parsing
// fails, an error is returned.
Protocol::Simple(data) => atoi::<u64>(data.as_bytes()).ok_or_else(|| MSG.into()),
Protocol::Bulk(data) => atoi::<u64>(&data).ok_or_else(|| MSG.into()),
Protocol => Err(format!("protocol error; expected int Protocol but got {:?}", Protocol).into()),
}
}

/// Ensure there are no more entries in the array
pub fn finish(&mut self) -> Result<(), ParseError> {
if self.parts.next().is_none() {
Ok(())
} else {
Err("protocol error; expected end of Protocol, but there was more".into())
}
}
}

impl From<String> for ParseError {
fn from(src: String) -> ParseError {
ParseError::Other(src.into())
}
}

impl From<&str> for ParseError {
fn from(src: &str) -> ParseError {
src.to_string().into()
}
}

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::EndOfStream => "protocol error; unexpected end of stream".fmt(f),
ParseError::Other(err) => err.fmt(f),
}
}
}

impl std::error::Error for ParseError {}
Loading

0 comments on commit 71338f2

Please sign in to comment.