use std::{
backtrace::{Backtrace, BacktraceStatus},
collections::BTreeMap,
convert::Infallible,
};
pub fn set_default(
annotations: &mut BTreeMap<String, String>,
key: &str,
value: impl std::fmt::Display,
) {
if !annotations.contains_key(key) {
annotations.insert(String::from(key), value.to_string());
}
}
pub fn prepend(
annotations: &mut BTreeMap<String, String>,
key: &str,
value: impl std::fmt::Display,
) {
if let Some(prev) = annotations.get_mut(key) {
*prev = format!("{}.{}", value, prev);
} else {
annotations.insert(String::from(key), value.to_string());
}
}
pub struct FieldName<'a>(pub &'a str);
impl std::fmt::Display for FieldName<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.0.is_empty() {
std::fmt::Display::fmt(self.0, f)
} else {
write!(f, "<empty>")
}
}
}
pub fn try_<T>(func: impl FnOnce() -> Result<T>) -> Result<T> {
func()
}
pub trait Context {
fn annotate(&self, annotations: &mut BTreeMap<String, String>);
}
impl Context for BTreeMap<String, String> {
fn annotate(&self, annotations: &mut BTreeMap<String, String>) {
for (k, v) in self {
if !annotations.contains_key(k) {
annotations.insert(k.to_owned(), v.to_owned());
}
}
}
}
pub trait ContextSupport {
type Output;
fn ctx<C: Context>(self, context: &C) -> Self::Output;
}
impl<T, E: Into<Error>> ContextSupport for Result<T, E> {
type Output = Result<T, Error>;
fn ctx<C: Context>(self, context: &C) -> Self::Output {
match self {
Ok(value) => Ok(value),
Err(err) => Err(err.ctx(context)),
}
}
}
impl<E: Into<Error>> ContextSupport for E {
type Output = Error;
fn ctx<C: Context>(self, context: &C) -> Self::Output {
let mut err = self.into();
context.annotate(&mut err.inner.annotations);
err
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct Error {
pub(crate) inner: Box<ErrorInner>,
}
pub(crate) struct ErrorInner {
kind: ErrorKind,
message: String,
backtrace: Box<Backtrace>,
cause: Option<Box<dyn std::error::Error + Send + Sync>>,
pub(crate) annotations: BTreeMap<String, String>,
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
self.inner.kind == other.inner.kind
&& self.inner.message == other.inner.message
&& self.inner.annotations == other.inner.annotations
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
Custom,
NullabilityViolation {
field: Option<String>,
},
MissingField {
field: String,
},
}
impl Error {
pub fn new(kind: ErrorKind, message: String) -> Self {
Self {
inner: Box::new(ErrorInner {
kind,
message,
backtrace: Box::new(Backtrace::capture()),
cause: None,
annotations: BTreeMap::new(),
}),
}
}
pub fn new_from<E: std::error::Error + Send + Sync + 'static>(
kind: ErrorKind,
message: String,
cause: E,
) -> Self {
let mut err = Self::new(kind, message);
err.inner.cause = Some(Box::new(cause));
err
}
}
impl Error {
pub fn message(&self) -> &str {
&self.inner.message
}
pub fn backtrace(&self) -> &Backtrace {
&self.inner.backtrace
}
pub(crate) fn annotations(&self) -> Option<&BTreeMap<String, String>> {
Some(&self.inner.annotations)
}
pub(crate) fn modify_message<F: FnOnce(&mut String)>(&mut self, func: F) {
func(&mut self.inner.message);
}
pub fn kind(&self) -> &ErrorKind {
&self.inner.kind
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Error: {msg}{annotations}\n{bt}",
msg = self.message(),
annotations = AnnotationsDisplay(self.annotations()),
bt = BacktraceDisplay(self.backtrace()),
)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Error: {msg}{annotations}",
msg = self.message(),
annotations = AnnotationsDisplay(self.annotations()),
)
}
}
struct AnnotationsDisplay<'a>(Option<&'a BTreeMap<String, String>>);
impl std::fmt::Display for AnnotationsDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Some(annotations) = self.0 else {
return Ok(());
};
if annotations.is_empty() {
return Ok(());
}
write!(f, " (")?;
for (idx, (key, value)) in annotations.iter().enumerate() {
if idx != 0 {
write!(f, ", ")?;
}
write!(f, "{key}: {value:?}")?;
}
write!(f, ")")
}
}
struct BacktraceDisplay<'a>(&'a Backtrace);
impl std::fmt::Display for BacktraceDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0.status() {
BacktraceStatus::Captured => write!(f, "Backtrace:\n{bt}", bt=self.0),
BacktraceStatus::Disabled => write!(f, "Backtrace not captured; set the `RUST_BACKTRACE=1` env variable to enable"),
_ => write!(f, "Backtrace not captured: most likely backtraces are not supported on the current platform"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.inner.cause.as_ref()?.as_ref())
}
}
impl serde::ser::Error for Error {
fn custom<T>(msg: T) -> Self
where
T: std::fmt::Display,
{
Self::new(ErrorKind::Custom, format!("serde::ser::Error: {}", msg))
}
}
impl serde::de::Error for Error {
fn custom<T>(msg: T) -> Self
where
T: std::fmt::Display,
{
Self::new(ErrorKind::Custom, format!("serde::de::Error: {}", msg))
}
}
macro_rules! fail {
(in $context:expr, $($tt:tt)*) => {
{
#[allow(unused)]
use $crate::internal::error::Context;
let mut err = $crate::internal::error::Error::new($crate::internal::error::ErrorKind::Custom, format!($($tt)*));
$context.annotate(&mut err.inner.annotations);
return Err(err);
}
};
($($tt:tt)*) => {
return Err($crate::internal::error::Error::new($crate::internal::error::ErrorKind::Custom, format!($($tt)*)))
};
}
pub(crate) use fail;
impl From<marrow::error::MarrowError> for Error {
fn from(err: marrow::error::MarrowError) -> Self {
Self::new_from(
ErrorKind::Custom,
format!("marrow::error::MarrowError: {err}"),
err,
)
}
}
impl From<chrono::format::ParseError> for Error {
fn from(err: chrono::format::ParseError) -> Self {
Self::new_from(ErrorKind::Custom, format!("chrono::ParseError: {err}"), err)
}
}
impl From<std::char::CharTryFromError> for Error {
fn from(err: std::char::CharTryFromError) -> Error {
Self::new_from(ErrorKind::Custom, format!("CharTryFromError: {err}"), err)
}
}
impl From<std::char::TryFromCharError> for Error {
fn from(err: std::char::TryFromCharError) -> Error {
Self::new_from(ErrorKind::Custom, format!("TryFromCharError: {err}"), err)
}
}
impl From<std::num::TryFromIntError> for Error {
fn from(err: std::num::TryFromIntError) -> Error {
Self::new_from(ErrorKind::Custom, format!("TryFromIntError: {err}"), err)
}
}
impl From<std::num::ParseIntError> for Error {
fn from(err: std::num::ParseIntError) -> Self {
Self::new_from(ErrorKind::Custom, format!("ParseIntError: {err}"), err)
}
}
impl From<std::fmt::Error> for Error {
fn from(err: std::fmt::Error) -> Self {
Self::new_from(ErrorKind::Custom, format!("std::fmt::Error: {err}"), err)
}
}
impl From<std::str::Utf8Error> for Error {
fn from(err: std::str::Utf8Error) -> Self {
Self::new_from(
ErrorKind::Custom,
format!("std::str::Utf8Error: {err}"),
err,
)
}
}
impl From<Infallible> for Error {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
impl From<bytemuck::PodCastError> for Error {
fn from(err: bytemuck::PodCastError) -> Self {
Self::new(ErrorKind::Custom, format!("bytemuck::PodCastError: {err}"))
}
}
pub type PanicOnError<T> = std::result::Result<T, PanicOnErrorError>;
#[derive(Debug)]
pub struct PanicOnErrorError;
impl<E: std::fmt::Display + std::fmt::Debug> From<E> for PanicOnErrorError {
fn from(value: E) -> Self {
panic!("{value:?}");
}
}
#[test]
fn error_can_be_converted_to_anyhow() {
fn func() -> anyhow::Result<()> {
Err(Error::new(ErrorKind::Custom, "dummy".to_string()))?;
Ok(())
}
assert!(func().is_err());
}
#[allow(unused)]
const _: () = {
trait AssertSendSync: Send + Sync {}
impl AssertSendSync for Error {}
impl<T: Send + Sync> AssertSendSync for Result<T> {}
};