use core::fmt;
#[non_exhaustive]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ErrorKind {
Empty,
NotToken,
InvalidByte,
BareCr,
LineFeed,
Nul,
Control,
UnterminatedQuote,
InvalidQuotedPair,
UnbalancedComment,
CommentTooDeep,
TooManyMembers,
TooManyEmptyElements,
TrailingSeparator,
ExpectedSeparator,
TrailingData,
DuplicateParameter,
MissingValue,
InvalidNumber,
NumberOverflow,
OutOfRange,
InvalidDate,
WeekdayMismatch,
InvalidMediaType,
Wildcard,
InvalidBoundary,
InvalidQValue,
InvalidEntityTag,
InvalidRange,
InvalidCharset,
UnsupportedCharset,
InvalidPercentEncoding,
InvalidUtf8,
ControlInDecoded,
InvalidHost,
InvalidPort,
InvalidAddress,
InvalidOrigin,
InvalidKey,
TooLong,
InvalidBase64,
InvalidEncodedWord,
SectionGap,
TooManySections,
InvalidAddrSpec,
LineTooLong,
Malformed,
TooManyHops,
BufferTooSmall,
}
impl ErrorKind {
#[must_use]
pub const fn message(self) -> &'static str {
match self {
Self::Empty => "empty value",
Self::NotToken => "not a token",
Self::InvalidByte => "invalid byte",
Self::BareCr => "bare CR",
Self::LineFeed => "line feed in value",
Self::Nul => "NUL byte",
Self::Control => "control byte",
Self::UnterminatedQuote => "unterminated quoted string",
Self::InvalidQuotedPair => "invalid quoted pair",
Self::UnbalancedComment => "unbalanced comment",
Self::CommentTooDeep => "comment nested too deeply",
Self::TooManyMembers => "too many members",
Self::TooManyEmptyElements => "too many empty list elements",
Self::TrailingSeparator => "trailing separator",
Self::ExpectedSeparator => "expected a separator",
Self::TrailingData => "trailing data",
Self::DuplicateParameter => "duplicate parameter",
Self::MissingValue => "missing value",
Self::InvalidNumber => "invalid number",
Self::NumberOverflow => "number overflow",
Self::OutOfRange => "value out of range",
Self::InvalidDate => "invalid date",
Self::WeekdayMismatch => "weekday does not match the date",
Self::InvalidMediaType => "invalid media type",
Self::Wildcard => "wildcard where a concrete value is required",
Self::InvalidBoundary => "invalid multipart boundary",
Self::InvalidQValue => "invalid quality value",
Self::InvalidEntityTag => "invalid entity tag",
Self::InvalidRange => "invalid range",
Self::InvalidCharset => "invalid charset",
Self::UnsupportedCharset => "unsupported charset",
Self::InvalidPercentEncoding => "invalid percent-encoding",
Self::InvalidUtf8 => "invalid UTF-8",
Self::ControlInDecoded => "control byte in decoded value",
Self::InvalidHost => "invalid host",
Self::InvalidPort => "invalid port",
Self::InvalidAddress => "invalid IP address",
Self::InvalidOrigin => "invalid origin",
Self::InvalidKey => "invalid structured field key",
Self::TooLong => "value too long",
Self::InvalidBase64 => "invalid base64",
Self::InvalidEncodedWord => "invalid encoded word",
Self::SectionGap => "RFC 2231 sections out of order",
Self::TooManySections => "too many RFC 2231 sections",
Self::InvalidAddrSpec => "invalid email address",
Self::LineTooLong => "line too long",
Self::Malformed => "malformed field",
Self::TooManyHops => "too many forwarding hops",
Self::BufferTooSmall => "output buffer too small",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.message())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ParseError {
kind: ErrorKind,
line: u8,
at: u32,
}
impl ParseError {
#[must_use]
pub const fn new(kind: ErrorKind, offset: usize) -> Self {
let at = if offset > u32::MAX as usize {
u32::MAX
} else {
offset as u32
};
Self { kind, line: 0, at }
}
#[must_use]
pub const fn kind(&self) -> ErrorKind {
self.kind
}
#[must_use]
pub const fn offset(&self) -> usize {
self.at as usize
}
#[must_use]
pub const fn line(&self) -> usize {
self.line as usize
}
pub(crate) const fn on_line(self, line: usize) -> Self {
let line = if line > u8::MAX as usize {
u8::MAX
} else {
line as u8
};
Self { line, ..self }
}
pub(crate) const fn shifted(self, by: usize) -> Self {
let by = if by > u32::MAX as usize {
u32::MAX
} else {
by as u32
};
Self {
at: self.at.saturating_add(by),
..self
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.line == 0 {
write!(f, "{} at offset {}", self.kind.message(), self.at)
} else {
write!(
f,
"{} at offset {} of field line {}",
self.kind.message(),
self.at,
self.line
)
}
}
}
impl core::error::Error for ParseError {}
pub(crate) type Result<T> = core::result::Result<T, ParseError>;
pub(crate) const fn err<T>(kind: ErrorKind, at: usize) -> Result<T> {
Err(ParseError::new(kind, at))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Full;
impl fmt::Display for Full {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("output sink is full")
}
}
impl core::error::Error for Full {}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum WriteError {
Full,
Forbidden {
at: u32,
},
OutOfRange,
NotToken,
InvalidSequence,
DuplicateName,
}
impl WriteError {
pub(crate) const fn forbidden(at: usize) -> Self {
let at = if at > u32::MAX as usize {
u32::MAX
} else {
at as u32
};
Self::Forbidden { at }
}
}
impl From<Full> for WriteError {
fn from(_: Full) -> Self {
Self::Full
}
}
impl fmt::Display for WriteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Full => f.write_str("output sink is full"),
Self::Forbidden { at } => write!(f, "forbidden byte at offset {at}"),
Self::OutOfRange => f.write_str("value out of range"),
Self::NotToken => f.write_str("not a token"),
Self::InvalidSequence => f.write_str("writer called out of order"),
Self::DuplicateName => f.write_str("duplicate parameter name"),
}
}
}
impl core::error::Error for WriteError {}
#[cfg(test)]
mod tests {
use std::string::ToString;
use super::*;
#[test]
fn test_parse_error_size_is_eight_bytes() {
assert_eq!(core::mem::size_of::<ParseError>(), 8);
assert_eq!(core::mem::size_of::<ErrorKind>(), 1);
}
#[test]
fn test_parse_error_offset_saturates() {
let e = ParseError::new(ErrorKind::Empty, usize::MAX);
assert_eq!(e.offset(), u32::MAX as usize);
assert_eq!(e.shifted(10).offset(), u32::MAX as usize);
assert_eq!(e.on_line(1000).line(), 255);
}
#[test]
fn test_parse_error_display_with_line() {
let e = ParseError::new(ErrorKind::InvalidByte, 3).on_line(2);
assert_eq!(e.to_string(), "invalid byte at offset 3 of field line 2");
assert_eq!(e.shifted(4).offset(), 7);
}
#[test]
fn test_every_message_is_nonempty_and_lowercase_led() {
use ErrorKind::*;
let all = [
Empty,
NotToken,
InvalidByte,
BareCr,
LineFeed,
Nul,
Control,
UnterminatedQuote,
InvalidQuotedPair,
UnbalancedComment,
CommentTooDeep,
TooManyMembers,
TooManyEmptyElements,
TrailingSeparator,
ExpectedSeparator,
TrailingData,
DuplicateParameter,
MissingValue,
InvalidNumber,
NumberOverflow,
OutOfRange,
InvalidDate,
WeekdayMismatch,
InvalidMediaType,
Wildcard,
InvalidBoundary,
InvalidQValue,
InvalidEntityTag,
InvalidRange,
InvalidCharset,
UnsupportedCharset,
InvalidPercentEncoding,
InvalidUtf8,
ControlInDecoded,
InvalidHost,
InvalidPort,
InvalidAddress,
InvalidOrigin,
InvalidKey,
TooLong,
InvalidBase64,
InvalidEncodedWord,
SectionGap,
TooManySections,
InvalidAddrSpec,
LineTooLong,
Malformed,
TooManyHops,
BufferTooSmall,
];
for k in all {
let m = k.message();
assert!(!m.is_empty());
assert_eq!(k.to_string(), m);
}
}
#[test]
fn test_write_error_display_and_from_full() {
assert_eq!(WriteError::from(Full), WriteError::Full);
assert_eq!(
WriteError::forbidden(7).to_string(),
"forbidden byte at offset 7"
);
assert_eq!(
WriteError::forbidden(usize::MAX),
WriteError::Forbidden { at: u32::MAX }
);
assert_eq!(Full.to_string(), "output sink is full");
assert_eq!(
WriteError::InvalidSequence.to_string(),
"writer called out of order"
);
assert_eq!(
WriteError::DuplicateName.to_string(),
"duplicate parameter name"
);
}
}