Skip to content

Commit

Permalink
feat: return an error when there is no matching open list or dict
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Oct 8, 2024
1 parent 4f4e4d3 commit f505891
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 51 deletions.
9 changes: 9 additions & 0 deletions src/parsers/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ pub enum Error {

#[error("Unrecognized first byte for new bencoded value: {0}, char: {1}")]
UnrecognizedFirstBencodeValueByte(u8, char),

#[error("Unexpected end of list or dict. No matching start for the list or dict end")]
NoMatchingStartForListOrDictEnd,

#[error("Unexpected end of list. Premature end of list")]
PrematureEndOfList,

#[error("Unexpected end of dictionary. Premature end of dictionary")]
PrematureEndOfDict,
}

impl From<std::io::Error> for Error {
Expand Down
95 changes: 44 additions & 51 deletions src/parsers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ use crate::rw::{
writer::Writer,
};

#[derive(Debug)]
pub enum BencodeType {
Integer,
String,
List,
Dict,
}

pub struct BencodeParser<R: Read> {
pub debug: bool,
pub iter: u64,
Expand Down Expand Up @@ -109,30 +117,25 @@ impl<R: Read> BencodeParser<R> {

match byte {
b'i' => {
// Begin of integer
self.begin_bencoded_value(writer)?;
self.begin_bencoded_value(BencodeType::Integer, writer)?;
integer::parse(&mut self.byte_reader, writer, byte)?;
}
b'0'..=b'9' => {
// Begin of string
self.begin_bencoded_value(writer)?;
self.begin_bencoded_value(BencodeType::String, writer)?;
string::parse(&mut self.byte_reader, writer, byte)?;
}
b'l' => {
// Begin of list
self.begin_bencoded_value(writer)?;
self.begin_bencoded_value(BencodeType::List, writer)?;
writer.write_byte(Self::JSON_ARRAY_BEGIN)?;
self.stack.push(State::ExpectingFirstListItemOrEnd);
}
b'd' => {
// Begin of dictionary
self.begin_bencoded_value(writer)?;
self.begin_bencoded_value(BencodeType::Dict, writer)?;
writer.write_byte(Self::JSON_OBJ_BEGIN)?;
self.stack.push(State::ExpectingFirstDictFieldOrEnd);
}
b'e' => {
// End of list or dictionary (not end of integer)
self.end_bencoded_value(writer)?;
self.end_list_or_dict(writer)?;
}
b'\n' => {
// Ignore line breaks at the beginning, the end or between values
Expand Down Expand Up @@ -171,8 +174,9 @@ impl<R: Read> BencodeParser<R> {
/// Will return an error if the writer can't write to the output.
pub fn begin_bencoded_value<W: Writer>(
&mut self,
_bencode_type: BencodeType,
writer: &mut W,
) -> Result<(), crate::rw::error::Error> {
) -> Result<(), error::Error> {
match self.stack.peek() {
State::Initial => {}
State::ExpectingFirstListItemOrEnd => {
Expand Down Expand Up @@ -210,10 +214,7 @@ impl<R: Read> BencodeParser<R> {
///
/// Will panic if the end of bencoded value (list or dictionary) was not
/// expected.
pub fn end_bencoded_value<W: Writer>(
&mut self,
writer: &mut W,
) -> Result<(), crate::rw::error::Error> {
pub fn end_list_or_dict<W: Writer>(&mut self, writer: &mut W) -> Result<(), error::Error> {
match self.stack.peek() {
State::ExpectingFirstListItemOrEnd | State::ExpectingNextListItem => {
writer.write_byte(Self::JSON_ARRAY_END)?;
Expand All @@ -222,12 +223,9 @@ impl<R: Read> BencodeParser<R> {
State::ExpectingFirstDictFieldOrEnd | State::ExpectingDictFieldKey => {
writer.write_byte(Self::JSON_OBJ_END)?;
}
State::ExpectingDictFieldValue | State::Initial => {
// todo: pass the type of value (list or dict) to customize the error message
panic!("error parsing end of list or dictionary, unexpected initial state on the stack")
}
State::ExpectingDictFieldValue => return Err(error::Error::PrematureEndOfDict),
State::Initial => return Err(error::Error::NoMatchingStartForListOrDictEnd),
}
// todo: sp < stack. What this conditions does in the C implementation?

Ok(())
}
Expand Down Expand Up @@ -300,21 +298,9 @@ mod tests {
fn it_should_fail_when_it_cannot_recognized_the_fist_byte_of_a_new_bencoded_value() {
let invalid_bencoded_value = b"a";

match parse(invalid_bencoded_value) {
Ok(_output) => {
panic!("expected error parsing input: {invalid_bencoded_value:?}")
}
Err(err) => match err {
crate::parsers::error::Error::Rw(_err) => panic!("unexpected error"),
crate::parsers::error::Error::UnexpectedByteParsingInteger(_byte) => {
panic!("unexpected error");
}
crate::parsers::error::Error::UnrecognizedFirstBencodeValueByte(byte, c) => {
assert_eq!(byte, b'a');
assert_eq!(c, 'a');
}
},
}
let result = parse(invalid_bencoded_value);

assert!(result.is_err());
}

mod integers {
Expand Down Expand Up @@ -347,20 +333,9 @@ mod tests {
fn it_should_fail_when_it_finds_an_invalid_byte() {
let int_with_invalid_byte = b"iae";

match parse(int_with_invalid_byte) {
Ok(_output) => panic!(
"expected error parsing invalid byte in integer: {int_with_invalid_byte:?}"
),
Err(err) => match err {
crate::parsers::error::Error::Rw(_err) => panic!("unexpected error"),
crate::parsers::error::Error::UnexpectedByteParsingInteger(byte) => {
assert_eq!(byte, b'a');
}
crate::parsers::error::Error::UnrecognizedFirstBencodeValueByte(_, _) => {
panic!("unexpected error")
}
},
}
let result = parse(int_with_invalid_byte);

assert!(result.is_err());
}
}

Expand Down Expand Up @@ -420,13 +395,22 @@ mod tests {
}

mod lists {
use crate::parsers::tests::to_json;
use crate::parsers::tests::{parse, to_json};

#[test]
fn empty_list() {
assert_eq!(to_json(b"le"), "[]".to_string());
}

#[test]
fn fail_when_when_it_receives_an_end_list_byte_without_the_matching_open_byte() {
let end_list_byte_without_start = b"e";

let result = parse(end_list_byte_without_start);

assert!(result.is_err());
}

mod with_one_item {
use crate::parsers::tests::to_json;

Expand Down Expand Up @@ -568,7 +552,7 @@ mod tests {
}

mod dictionary {
use crate::parsers::tests::to_json;
use crate::parsers::tests::{parse, to_json};

// Note: Keys must be bencoded strings.

Expand All @@ -585,6 +569,15 @@ mod tests {
assert_eq!(to_json(b"de"), "{}".to_string());
}

#[test]
fn fail_when_when_it_receives_an_end_dict_byte_without_the_matching_open_byte() {
let end_dict_byte_without_start = b"e";

let result = parse(end_dict_byte_without_start);

assert!(result.is_err());
}

mod with_one_key {
use crate::parsers::tests::to_json;

Expand Down

0 comments on commit f505891

Please sign in to comment.