use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
InvalidArgument,
InvalidData,
}
impl ErrorKind {
pub const fn into_static(self) -> &'static str {
match self {
ErrorKind::InvalidArgument => "InvalidArgument",
ErrorKind::InvalidData => "InvalidData",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.into_static())
}
}
pub struct Error {
kind: ErrorKind,
message: String,
context: Vec<(&'static str, String)>,
}
impl Error {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
context: vec![],
}
}
pub fn with_context(mut self, key: &'static str, value: impl ToString) -> Self {
self.context.push((key, value.to_string()));
self
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
self.message.as_str()
}
}
#[allow(dead_code)] impl Error {
pub(crate) fn invalid_argument(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::InvalidArgument, msg)
}
pub(crate) fn deserial(msg: impl Into<String>) -> Self {
Self::new(ErrorKind::InvalidData, msg)
}
pub(crate) fn insufficient_data(msg: impl fmt::Display) -> Self {
Self::deserial(format!("insufficient data: {msg}"))
}
pub(crate) fn insufficient_data_of(context: &'static str, msg: impl fmt::Display) -> Self {
Self::deserial(format!("insufficient data ({context}): {msg}"))
}
pub(crate) fn invalid_family(expected: u8, actual: u8, name: &'static str) -> Self {
Self::deserial(format!(
"invalid family: expected {expected} ({name}), got {actual}"
))
}
pub(crate) fn invalid_preamble_longs(expected: &[u8], actual: u8) -> Self {
Error::deserial(format!(
"invalid preamble longs: expected {expected:?}, got {actual}"
))
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
let mut de = f.debug_struct("Error");
de.field("kind", &self.kind);
de.field("message", &self.message);
de.field("context", &self.context);
return de.finish();
}
write!(f, "{}", self.kind)?;
if !self.message.is_empty() {
write!(f, " => {}", self.message)?;
}
writeln!(f)?;
if !self.context.is_empty() {
writeln!(f)?;
writeln!(f, "Context:")?;
for (k, v) in self.context.iter() {
writeln!(f, " {k}: {v}")?;
}
}
Ok(())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if !self.context.is_empty() {
write!(f, ", context: {{ ")?;
for (i, (k, v)) in self.context.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}: {}", k, v)?;
}
write!(f, " }}")?;
}
if !self.message.is_empty() {
write!(f, " => {}", self.message)?;
}
Ok(())
}
}
impl std::error::Error for Error {}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use super::*;
#[test]
fn test_format_consistency() {
let err = Error::new(ErrorKind::InvalidArgument, "something went wrong");
assert_snapshot!(err, @"InvalidArgument => something went wrong",);
}
#[test]
fn test_format_with_multiple_contexts() {
let err = Error::new(ErrorKind::InvalidData, "parsing failed")
.with_context("index", 42)
.with_context("file", "foo");
assert_snapshot!(err, @"InvalidData, context: { index: 42, file: foo } => parsing failed");
}
}