Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/task/handle_custom_errors'
Browse files Browse the repository at this point in the history
  • Loading branch information
0ndorio committed Feb 25, 2019
2 parents 4e53847 + fa43d6f commit 166b51b
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 132 deletions.
42 changes: 22 additions & 20 deletions src/decoding/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
decoding::Object,
decoding::{Error, Object},
state_tracker::{StateTracker, StructureError, Token},
};

Expand All @@ -11,7 +11,7 @@ use crate::{
pub struct Decoder<'a> {
source: &'a [u8],
offset: usize,
state: StateTracker<&'a [u8]>,
state: StateTracker<&'a [u8], Error>,
}

impl<'ser> Decoder<'ser> {
Expand Down Expand Up @@ -133,7 +133,7 @@ impl<'ser> Decoder<'ser> {
Ok(ival)
}

fn raw_next_token(&mut self) -> Result<Token<'ser>, StructureError> {
fn raw_next_token(&mut self) -> Result<Token<'ser>, Error> {
let token = match self.take_byte().ok_or(StructureError::UnexpectedEof)? as char {
'e' => Token::End,
'l' => Token::List,
Expand All @@ -150,19 +150,19 @@ impl<'ser> Decoder<'ser> {
Token::String(self.take_chunk(len).ok_or(StructureError::UnexpectedEof)?)
},
tok => {
return Err(StructureError::SyntaxError(format!(
return Err(Error::from(StructureError::SyntaxError(format!(
"Invalid token starting with {:?} at offset {}",
tok,
self.offset - 1
)));
))));
},
};

Ok(token)
}

/// Read the next token. Returns Ok(Some(token)) if a token was successfully read,
fn next_token(&mut self) -> Result<Option<Token<'ser>>, StructureError> {
fn next_token(&mut self) -> Result<Option<Token<'ser>>, Error> {
self.state.check_error()?;

if self.offset == self.source.len() {
Expand All @@ -189,7 +189,7 @@ impl<'ser> Decoder<'ser> {
pub struct Tokens<'a>(Decoder<'a>);

impl<'a> Iterator for Tokens<'a> {
type Item = Result<Token<'a>, StructureError>;
type Item = Result<Token<'a>, Error>;

fn next(&mut self) -> Option<Self::Item> {
// Only report an error once
Expand All @@ -216,7 +216,7 @@ impl<'ser> Decoder<'ser> {
/// Note that complex objects (lists and dicts) are not fully validated before being
/// returned from this method, so you may still get an error while decoding the contents
/// of the object
pub fn next_object<'obj>(&'obj mut self) -> Result<Option<Object<'obj, 'ser>>, StructureError> {
pub fn next_object<'obj>(&'obj mut self) -> Result<Option<Object<'obj, 'ser>>, Error> {
use self::Token::*;
Ok(match self.next_token()? {
None | Some(End) => None,
Expand Down Expand Up @@ -258,7 +258,7 @@ impl<'obj, 'ser: 'obj> DictDecoder<'obj, 'ser> {
/// at the end of the dictionary
pub fn next_pair<'item>(
&'item mut self,
) -> Result<Option<(&'ser [u8], Object<'item, 'ser>)>, StructureError> {
) -> Result<Option<(&'ser [u8], Object<'item, 'ser>)>, Error> {
if self.finished {
return Ok(None);
}
Expand All @@ -281,15 +281,15 @@ impl<'obj, 'ser: 'obj> DictDecoder<'obj, 'ser> {
/// Consume (and validate the structure of) the rest of the items from the
/// dictionary. This method should be used to check for encoding errors if
/// [`DictDecoder::next_pair`] is not called until it returns `Ok(None)`.
pub fn consume_all(&mut self) -> Result<(), StructureError> {
pub fn consume_all(&mut self) -> Result<(), Error> {
while let Some(_) = self.next_pair()? {
// just drop the items
}
Ok(())
}

/// Get the raw bytes that made up this dictionary
pub fn into_raw(mut self) -> Result<&'ser [u8], StructureError> {
pub fn into_raw(mut self) -> Result<&'ser [u8], Error> {
self.consume_all()?;
Ok(&self.decoder.source[self.start_point..self.decoder.offset])
}
Expand All @@ -313,9 +313,7 @@ impl<'obj, 'ser: 'obj> ListDecoder<'obj, 'ser> {
}

/// Get the next item from the list. Returns `Ok(None)` at the end of the list
pub fn next_object<'item>(
&'item mut self,
) -> Result<Option<Object<'item, 'ser>>, StructureError> {
pub fn next_object<'item>(&'item mut self) -> Result<Option<Object<'item, 'ser>>, Error> {
if self.finished {
return Ok(None);
}
Expand All @@ -333,15 +331,15 @@ impl<'obj, 'ser: 'obj> ListDecoder<'obj, 'ser> {
/// [`ListDecoder::next_object`] is not called until it returns [`Ok(())`].
///
/// [`Ok(())`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Ok
pub fn consume_all(&mut self) -> Result<(), StructureError> {
pub fn consume_all(&mut self) -> Result<(), Error> {
while let Some(_) = self.next_object()? {
// just drop the items
}
Ok(())
}

/// Get the raw bytes that made up this list
pub fn into_raw(mut self) -> Result<&'ser [u8], StructureError> {
pub fn into_raw(mut self) -> Result<&'ser [u8], Error> {
self.consume_all()?;
Ok(&self.decoder.source[self.start_point..self.decoder.offset])
}
Expand All @@ -365,7 +363,7 @@ mod test {
static SIMPLE_MSG: &'static [u8] = b"d3:bari1e3:fooli2ei3eee";

fn decode_tokens(msg: &[u8]) -> Vec<Token> {
let tokens: Vec<Result<Token, StructureError>> = Decoder::new(msg).tokens().collect();
let tokens: Vec<Result<Token, Error>> = Decoder::new(msg).tokens().collect();
if tokens.iter().all(Result::is_ok) {
tokens.into_iter().map(Result::unwrap).collect()
} else {
Expand All @@ -377,7 +375,7 @@ mod test {
}

fn decode_err(msg: &[u8], err_regex: &str) {
let mut tokens: Vec<Result<Token, StructureError>> = Decoder::new(msg).tokens().collect();
let mut tokens: Vec<Result<Token, Error>> = Decoder::new(msg).tokens().collect();
if tokens.iter().all(Result::is_ok) {
panic!("Unexpected parse success: {:?}", tokens);
} else {
Expand Down Expand Up @@ -501,14 +499,18 @@ mod test {
fn dict_drop_should_consume_struct() {
let mut decoder = Decoder::new(b"d3:fooi1e3:quxi2eei1000e");
drop(decoder.next_object());
assert_eq!(decoder.tokens().next(), Some(Ok(Token::Num("1000"))));

let token = decoder.tokens().next().unwrap().unwrap();
assert_eq!(token, Token::Num("1000"));
}

#[test]
fn list_drop_should_consume_struct() {
let mut decoder = Decoder::new(b"li1ei2ei3eei1000e");
drop(decoder.next_object());
assert_eq!(decoder.tokens().next(), Some(Ok(Token::Num("1000"))));

let token = decoder.tokens().next().unwrap().unwrap();
assert_eq!(token, Token::Num("1000"));
}

#[test]
Expand Down
59 changes: 24 additions & 35 deletions src/decoding/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::{
fmt::{self, Display, Formatter},
num, str, string,
sync::Arc,
};

use failure::Fail;

use crate::state_tracker::StructureError;

#[derive(Debug, Fail)]
#[derive(Debug, Clone, Fail)]
pub struct Error {
#[fail(context)]
context: Option<String>,
Expand All @@ -16,16 +16,16 @@ pub struct Error {
}

/// An enumeration of potential errors that appear during bencode deserialization.
#[derive(Debug, Fail)]
#[derive(Debug, Clone, Fail)]
pub enum ErrorKind {
/// Error that occurs if the serialized structure contains invalid information.
#[fail(display = "malformed content discovered: {}", _0)]
MalformedContent(failure::Error),
MalformedContent(Arc<failure::Error>),
/// Error that occurs if the serialized structure is incomplete.
#[fail(display = "missing field: {}", _0)]
MissingField(String),
/// Error in the bencode structure (e.g. a missing field end separator).
#[fail(display = "bencode encoding corrupted")]
#[fail(display = "bencode encoding corrupted ({})", _0)]
StructureError(#[fail(cause)] StructureError),
/// Error that occurs if the serialized structure contains an unexpected field.
#[fail(display = "unexpected field: {}", _0)]
Expand All @@ -39,24 +39,6 @@ pub trait ResultExt {
fn context(self, context: impl std::fmt::Display) -> Self;
}

impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match &self.context {
Some(context) => write!(f, "Error: {} in {}", self.error, context),
None => write!(f, "Error: {}", self.error),
}
}
}

impl From<ErrorKind> for Error {
fn from(error: ErrorKind) -> Self {
Error {
context: None,
error,
}
}
}

impl Error {
pub fn context(mut self, context: impl Display) -> Self {
if let Some(current) = self.context.as_mut() {
Expand All @@ -71,7 +53,8 @@ impl Error {
/// Raised when there is a general error while deserializing a type.
/// The message should not be capitalized and should not end with a period.
pub fn malformed_content(cause: impl Into<failure::Error>) -> Error {
Self::from(ErrorKind::MalformedContent(cause.into()))
let error = Arc::new(cause.into());
Self::from(ErrorKind::MalformedContent(error))
}

/// Returns a `Error::MissingField` which contains the name of the field.
Expand All @@ -93,26 +76,32 @@ impl Error {
}
}

impl From<StructureError> for Error {
fn from(error: StructureError) -> Self {
Self::from(ErrorKind::StructureError(error))
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match &self.context {
Some(context) => write!(f, "Error: {} in {}", self.error, context),
None => write!(f, "Error: {}", self.error),
}
}
}

impl From<num::ParseIntError> for Error {
fn from(error: num::ParseIntError) -> Self {
Self::malformed_content(error)
impl From<StructureError> for Error {
fn from(error: StructureError) -> Self {
Self::from(ErrorKind::StructureError(error))
}
}

impl From<str::Utf8Error> for Error {
fn from(error: str::Utf8Error) -> Self {
Self::malformed_content(error)
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Self {
context: None,
error: kind,
}
}
}

impl From<string::FromUtf8Error> for Error {
fn from(error: string::FromUtf8Error) -> Self {
impl<T: std::error::Error + Send + Sync + 'static> From<T> for Error {
fn from(error: T) -> Self {
Self::malformed_content(error)
}
}
Expand Down
45 changes: 45 additions & 0 deletions src/decoding/from_bencode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,48 @@ impl FromBencode for AsString<Vec<u8>> {
object.try_into_bytes().map(Vec::from).map(AsString)
}
}

#[cfg(test)]
mod test {

use super::*;
use crate::encoding::AsString;

#[test]
fn from_bencode_to_string_should_work_with_valid_input() {
let expected_message = "hello";
let serialized_message =
format!("{}:{}", expected_message.len(), expected_message).into_bytes();

let decoded_message = String::from_bencode(&serialized_message).unwrap();
assert_eq!(expected_message, decoded_message);
}

#[test]
fn from_bencode_to_as_string_should_work_with_valid_input() {
let expected_message = "hello";
let serialized_message =
format!("{}:{}", expected_message.len(), expected_message).into_bytes();

let decoded_vector = AsString::from_bencode(&serialized_message).unwrap();
assert_eq!(expected_message.as_bytes(), &decoded_vector.0[..]);
}

#[test]
#[should_panic(expected = "Num")]
fn from_bencode_to_as_string_should_fail_for_integer() {
AsString::<Vec<u8>>::from_bencode(&b"i1e"[..]).unwrap();
}

#[test]
#[should_panic(expected = "NestingTooDeep")]
fn from_bencode_to_as_string_should_fail_for_list() {
AsString::<Vec<u8>>::from_bencode(&b"l1:ae"[..]).unwrap();
}

#[test]
#[should_panic(expected = "NestingTooDeep")]
fn from_bencode_to_as_string_should_fail_for_dictionary() {
AsString::<Vec<u8>>::from_bencode(&b"d1:a1:ae"[..]).unwrap();
}
}
2 changes: 1 addition & 1 deletion src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ mod to_bencode;

pub use self::{
encoder::{Encoder, SingleItemEncoder, SortedDictEncoder, UnsortedDictEncoder},
error::Error,
error::{Error, ErrorKind},
printable_integer::PrintableInteger,
to_bencode::{AsString, ToBencode},
};
Loading

0 comments on commit 166b51b

Please sign in to comment.