use alloc::{
format,
str::Utf8Error,
string::{FromUtf8Error, String, ToString},
};
use core::{fmt::Display, num::ParseIntError};
#[cfg(feature = "std")]
use std::sync::Arc;
use thiserror::Error;
use crate::state_tracker;
#[derive(Debug, Clone)]
pub struct Error {
context: Option<String>,
kind: ErrorKind,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(c) = &self.context {
write!(f, "\nin context:\n\t{}", c)?;
}
Ok(())
}
}
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.kind.source()
}
}
#[derive(Debug, Clone, Error)]
#[non_exhaustive]
pub enum ErrorKind {
#[cfg(feature = "std")]
#[error("malformed content discovered: {source}")]
MalformedContent {
#[source]
source: Arc<dyn std::error::Error + Send + Sync>,
},
#[cfg(not(feature = "std"))]
#[error("malformed content discovered")]
MalformedContent,
#[error("missing field: {field}")]
MissingField { field: String },
#[error("bencode encoding corrupted ({source})")]
StructureError {
source: state_tracker::StructureError,
},
#[error("unexpected field: {field}")]
UnexpectedField { field: String },
#[error("discovered {discovered} but expected {expected}")]
UnexpectedToken {
expected: String,
discovered: String,
},
}
pub trait ResultExt {
fn context(self, context: impl Display) -> Self;
}
impl Error {
pub fn context(mut self, context: impl Display) -> Self {
if let Some(current) = self.context.as_mut() {
*current = format!("{current}\n\t{context}");
} else {
self.context = Some(context.to_string());
}
self
}
#[cfg(feature = "std")]
pub fn malformed_content<SourceT>(source: SourceT) -> Self
where
SourceT: std::error::Error + Send + Sync + 'static,
{
let error = Arc::new(source);
ErrorKind::MalformedContent { source: error }.into()
}
#[cfg(not(feature = "std"))]
pub fn malformed_content<T>(_cause: T) -> Self {
Self::from(ErrorKind::MalformedContent)
}
pub fn missing_field(field_name: impl Display) -> Self {
Error::from(ErrorKind::MissingField {
field: field_name.to_string(),
})
}
pub fn unexpected_field(field_name: impl Display) -> Self {
Error::from(ErrorKind::UnexpectedField {
field: field_name.to_string(),
})
}
pub fn unexpected_token(expected: impl Display, discovered: impl Display) -> Self {
Error::from(ErrorKind::UnexpectedToken {
expected: expected.to_string(),
discovered: discovered.to_string(),
})
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Self {
context: None,
kind,
}
}
}
impl From<state_tracker::StructureError> for Error {
fn from(error: state_tracker::StructureError) -> Self {
Self::from(ErrorKind::StructureError { source: error })
}
}
impl From<FromUtf8Error> for Error {
fn from(err: FromUtf8Error) -> Self {
Self::malformed_content(err)
}
}
impl From<Utf8Error> for Error {
fn from(err: Utf8Error) -> Self {
Self::malformed_content(err)
}
}
impl From<ParseIntError> for Error {
fn from(err: ParseIntError) -> Self {
Self::malformed_content(err)
}
}
impl<T> ResultExt for Result<T, Error> {
fn context(self, context: impl Display) -> Self {
self.map_err(|err| err.context(context))
}
}
#[test]
fn decoding_errors_are_sync_send() {
use crate::decoding::error::{Error, ErrorKind};
fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}
is_send::<Error>();
is_send::<ErrorKind>();
is_sync::<Error>();
is_sync::<ErrorKind>();
}
#[test]
fn error_context_is_displayed() {
let e:Result<(), Error> = Err(Error::missing_field("aaa")).context("bbb").context("ccc");
let e = e.unwrap_err().to_string();
println!("{}", &e);
assert!(e.contains("aaa\n"));
assert!(e.contains("in context:\n"));
assert!(e.contains("bbb\n"));
assert!(e.contains("ccc"));
}