Skip to content

Commit

Permalink
Wrap errors in a new struct type
Browse files Browse the repository at this point in the history
This will make it easier to provide additional information, such as the
position at which parsing failed.
  • Loading branch information
apasel422 committed Feb 13, 2025
1 parent 1324bde commit 0196dbf
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 197 deletions.
22 changes: 22 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::fmt;

/// An error that occurs during parsing or serialization.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Error {
msg: &'static str,
}

impl Error {
pub(crate) fn new(msg: &'static str) -> Self {
Self { msg }
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.msg)
}
}

impl std::error::Error for Error {}
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ assert_eq!(
```
*/

mod error;
mod parser;
mod ref_serializer;
mod serializer;
Expand All @@ -180,11 +181,12 @@ pub use rust_decimal::{
Decimal,
};

pub use error::Error;
pub use parser::{ParseMore, Parser};
pub use ref_serializer::{RefDictSerializer, RefItemSerializer, RefListSerializer};
pub use serializer::SerializeValue;

type SFVResult<T> = std::result::Result<T, &'static str>;
type SFVResult<T> = std::result::Result<T, Error>;

/// Represents `Item` type structured field value.
/// Can be used as a member of `List` or `Dictionary`.
Expand Down
81 changes: 51 additions & 30 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::utils;
use crate::{
BareItem, Decimal, Dictionary, InnerList, Item, List, ListEntry, Num, Parameters, SFVResult,
BareItem, Decimal, Dictionary, Error, InnerList, Item, List, ListEntry, Num, Parameters,
SFVResult,
};

trait ParseValue {
Expand Down Expand Up @@ -55,14 +56,16 @@ impl ParseValue for List {

if let Some(c) = parser.input.next() {
if c != b',' {
return Err("parse_list: trailing characters after list member");
return Err(Error::new(
"parse_list: trailing characters after list member",
));
}
}

parser.consume_ows_chars();

if parser.input.peek().is_none() {
return Err("parse_list: trailing comma");
return Err(Error::new("parse_list: trailing comma"));
}
}

Expand Down Expand Up @@ -99,14 +102,16 @@ impl ParseValue for Dictionary {

if let Some(c) = parser.input.next() {
if c != b',' {
return Err("parse_dict: trailing characters after dictionary member");
return Err(Error::new(
"parse_dict: trailing characters after dictionary member",
));
}
}

parser.consume_ows_chars();

if parser.input.peek().is_none() {
return Err("parse_dict: trailing comma");
return Err(Error::new("parse_dict: trailing comma"));
}
}
Ok(dict)
Expand Down Expand Up @@ -172,7 +177,7 @@ impl<'a> Parser<'a> {
self.consume_sp_chars();

if self.input.peek().is_some() {
return Err("parse: trailing characters after parsed value");
return Err(Error::new("parse: trailing characters after parsed value"));
};
Ok(output)
}
Expand All @@ -197,7 +202,9 @@ impl<'a> Parser<'a> {
// https://httpwg.org/specs/rfc8941.html#parse-innerlist

if Some(b'(') != self.input.next() {
return Err("parse_inner_list: input does not start with '('");
return Err(Error::new(
"parse_inner_list: input does not start with '('",
));
}

let mut inner_list = Vec::new();
Expand All @@ -218,18 +225,20 @@ impl<'a> Parser<'a> {

if let Some(c) = self.input.peek() {
if c != &b' ' && c != &b')' {
return Err("parse_inner_list: bad delimitation");
return Err(Error::new("parse_inner_list: bad delimitation"));
}
}
}

Err("parse_inner_list: the end of the inner list was not found")
Err(Error::new(
"parse_inner_list: the end of the inner list was not found",
))
}

pub(crate) fn parse_bare_item(&mut self) -> SFVResult<BareItem> {
// https://httpwg.org/specs/rfc8941.html#parse-bare-item
if self.input.peek().is_none() {
return Err("parse_bare_item: empty item");
return Err(Error::new("parse_bare_item: empty item"));
}

match self.input.peek() {
Expand All @@ -243,58 +252,62 @@ impl<'a> Parser<'a> {
Num::Decimal(val) => Ok(BareItem::Decimal(val)),
Num::Integer(val) => Ok(BareItem::Integer(val)),
},
_ => Err("parse_bare_item: item type can't be identified"),
_ => Err(Error::new("parse_bare_item: item type can't be identified")),
}
}

pub(crate) fn parse_bool(&mut self) -> SFVResult<bool> {
// https://httpwg.org/specs/rfc8941.html#parse-boolean

if self.input.next() != Some(b'?') {
return Err("parse_bool: first character is not '?'");
return Err(Error::new("parse_bool: first character is not '?'"));
}

match self.input.next() {
Some(b'0') => Ok(false),
Some(b'1') => Ok(true),
_ => Err("parse_bool: invalid variant"),
_ => Err(Error::new("parse_bool: invalid variant")),
}
}

pub(crate) fn parse_string(&mut self) -> SFVResult<String> {
// https://httpwg.org/specs/rfc8941.html#parse-string

if self.input.next() != Some(b'"') {
return Err("parse_string: first character is not '\"'");
return Err(Error::new("parse_string: first character is not '\"'"));
}

let mut output_string = String::from("");
while let Some(curr_char) = self.input.next() {
match curr_char {
b'"' => return Ok(output_string),
0x00..=0x1f | 0x7f..=0xff => return Err("parse_string: invalid string character"),
0x00..=0x1f | 0x7f..=0xff => {
return Err(Error::new("parse_string: invalid string character"))
}
b'\\' => match self.input.next() {
Some(c @ b'\\' | c @ b'\"') => {
output_string.push(c as char);
}
None => return Err("parse_string: last input character is '\\'"),
_ => return Err("parse_string: disallowed character after '\\'"),
None => return Err(Error::new("parse_string: last input character is '\\'")),
_ => return Err(Error::new("parse_string: disallowed character after '\\'")),
},
_ => output_string.push(curr_char as char),
}
}
Err("parse_string: no closing '\"'")
Err(Error::new("parse_string: no closing '\"'"))
}

pub(crate) fn parse_token(&mut self) -> SFVResult<String> {
// https://httpwg.org/specs/rfc8941.html#parse-token

if let Some(first_char) = self.input.peek() {
if !first_char.is_ascii_alphabetic() && first_char != &b'*' {
return Err("parse_token: first character is not ALPHA or '*'");
return Err(Error::new(
"parse_token: first character is not ALPHA or '*'",
));
}
} else {
return Err("parse_token: empty input string");
return Err(Error::new("parse_token: empty input string"));
}

let mut output_string = String::from("");
Expand All @@ -305,7 +318,7 @@ impl<'a> Parser<'a> {

match self.input.next() {
Some(c) => output_string.push(c as char),
None => return Err("parse_token: end of the string"),
None => return Err(Error::new("parse_token: end of the string")),
}
}
Ok(output_string)
Expand All @@ -315,21 +328,21 @@ impl<'a> Parser<'a> {
// https://httpwg.org/specs/rfc8941.html#parse-binary

if self.input.next() != Some(b':') {
return Err("parse_byte_seq: first char is not ':'");
return Err(Error::new("parse_byte_seq: first char is not ':'"));
}

let mut b64_content = vec![];
loop {
match self.input.next() {
Some(b':') => break,
Some(c) => b64_content.push(c),
None => return Err("parse_byte_seq: no closing ':'"),
None => return Err(Error::new("parse_byte_seq: no closing ':'")),
}
}

match base64::Engine::decode(&utils::BASE64, b64_content) {
Ok(content) => Ok(content),
Err(_) => Err("parse_byte_seq: decoding error"),
Err(_) => Err(Error::new("parse_byte_seq: decoding error")),
}
}

Expand All @@ -352,7 +365,7 @@ impl<'a> Parser<'a> {
self.input.next();
char_to_i64(c)
}
_ => return Err("parse_number: expected digit"),
_ => return Err(Error::new("parse_number: expected digit")),
};

let mut digits = 1;
Expand All @@ -361,15 +374,17 @@ impl<'a> Parser<'a> {
match self.input.peek() {
Some(b'.') => {
if digits > 12 {
return Err("parse_number: too many digits before decimal point");
return Err(Error::new(
"parse_number: too many digits before decimal point",
));
}
self.input.next();
break;
}
Some(&c @ b'0'..=b'9') => {
digits += 1;
if digits > 15 {
return Err("parse_number: too many digits");
return Err(Error::new("parse_number: too many digits"));
}
self.input.next();
magnitude = magnitude * 10 + char_to_i64(c);
Expand All @@ -382,7 +397,9 @@ impl<'a> Parser<'a> {

while let Some(&c @ b'0'..=b'9') = self.input.peek() {
if digits == 3 {
return Err("parse_number: too many digits after decimal point");
return Err(Error::new(
"parse_number: too many digits after decimal point",
));
}

self.input.next();
Expand All @@ -391,7 +408,7 @@ impl<'a> Parser<'a> {
}

if digits == 0 {
Err("parse_number: trailing decimal point")
Err(Error::new("parse_number: trailing decimal point"))
} else {
Ok(Num::Decimal(Decimal::from_i128_with_scale(
(sign * magnitude) as i128,
Expand Down Expand Up @@ -433,7 +450,11 @@ impl<'a> Parser<'a> {
pub(crate) fn parse_key(&mut self) -> SFVResult<String> {
match self.input.peek() {
Some(c) if c == &b'*' || c.is_ascii_lowercase() => (),
_ => return Err("parse_key: first character is not lcalpha or '*'"),
_ => {
return Err(Error::new(
"parse_key: first character is not lcalpha or '*'",
))
}
}

let mut output = String::new();
Expand Down
14 changes: 10 additions & 4 deletions src/ref_serializer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::serializer::Serializer;
use crate::{AsRefBareItem, RefBareItem, SFVResult};
use crate::{AsRefBareItem, Error, RefBareItem, SFVResult};
use std::marker::PhantomData;

/// Serializes `Item` field value components incrementally.
Expand Down Expand Up @@ -94,7 +94,9 @@ impl<'a> RefListSerializer<'a> {

pub fn parameter(self, name: &str, value: impl AsRefBareItem) -> SFVResult<Self> {
if self.buffer.is_empty() {
return Err("parameters must be serialized after bare item or inner list");
return Err(Error::new(
"parameters must be serialized after bare item or inner list",
));
}
Serializer::serialize_parameter(name, value, self.buffer)?;
Ok(RefListSerializer {
Expand Down Expand Up @@ -170,7 +172,9 @@ impl<'a> RefDictSerializer<'a> {

pub fn parameter(self, name: &str, value: impl AsRefBareItem) -> SFVResult<Self> {
if self.buffer.is_empty() {
return Err("parameters must be serialized after bare item or inner list");
return Err(Error::new(
"parameters must be serialized after bare item or inner list",
));
}
Serializer::serialize_parameter(name, value, self.buffer)?;
Ok(RefDictSerializer {
Expand Down Expand Up @@ -212,7 +216,9 @@ impl<'a, T: Container<'a>> RefInnerListSerializer<'a, T> {

pub fn inner_list_parameter(self, name: &str, value: impl AsRefBareItem) -> SFVResult<Self> {
if self.buffer.is_empty() {
return Err("parameters must be serialized after bare item or inner list");
return Err(Error::new(
"parameters must be serialized after bare item or inner list",
));
}
Serializer::serialize_parameter(name, value, self.buffer)?;
Ok(RefInnerListSerializer {
Expand Down
Loading

0 comments on commit 0196dbf

Please sign in to comment.