use crate::util::sync::Arc;
macro_rules! err {
($($tt:tt)*) => {{
crate::error::Error::adhoc_from_args(format_args!($($tt)*))
}}
}
pub(crate) use err;
#[derive(Clone)]
pub struct Error {
inner: Arc<ErrorInner>,
}
#[derive(Debug)]
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
struct ErrorInner {
kind: ErrorKind,
#[cfg(feature = "alloc")]
cause: Option<Error>,
}
#[derive(Debug)]
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
enum ErrorKind {
Adhoc(AdhocError),
Range(RangeError),
#[allow(dead_code)] FilePath(FilePathError),
#[allow(dead_code)] IO(IOError),
}
impl Error {
#[cfg(feature = "alloc")]
pub(crate) fn adhoc<'a>(message: impl core::fmt::Display + 'a) -> Error {
Error::from(ErrorKind::Adhoc(AdhocError::from_display(message)))
}
pub(crate) fn adhoc_from_args<'a>(
message: core::fmt::Arguments<'a>,
) -> Error {
Error::from(ErrorKind::Adhoc(AdhocError::from_args(message)))
}
pub(crate) fn adhoc_from_static_str(message: &'static str) -> Error {
Error::from(ErrorKind::Adhoc(AdhocError::from_static_str(message)))
}
pub(crate) fn range(
what: &'static str,
given: impl Into<i128>,
min: impl Into<i128>,
max: impl Into<i128>,
) -> Error {
Error::from(ErrorKind::Range(RangeError::new(what, given, min, max)))
}
#[cfg(feature = "std")]
pub(crate) fn io(err: std::io::Error) -> Error {
Error::from(ErrorKind::IO(IOError { err }))
}
#[cfg(feature = "tzdb-zoneinfo")]
pub(crate) fn path(self, path: impl Into<std::path::PathBuf>) -> Error {
let err = Error::from(ErrorKind::FilePath(FilePathError {
path: path.into(),
}));
self.context(err)
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "alloc")]
{
let mut err = self;
loop {
write!(f, "{}", err.inner.kind)?;
err = match err.inner.cause.as_ref() {
None => break,
Some(err) => err,
};
write!(f, ": ")?;
}
Ok(())
}
#[cfg(not(feature = "alloc"))]
{
write!(f, "{}", self.inner.kind)
}
}
}
impl core::fmt::Debug for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if !f.alternate() {
core::fmt::Display::fmt(self, f)
} else {
#[cfg(feature = "alloc")]
{
f.debug_struct("Error")
.field("kind", &self.inner.kind)
.field("cause", &self.inner.cause)
.finish()
}
#[cfg(not(feature = "alloc"))]
{
f.debug_struct("Error")
.field("kind", &self.inner.kind)
.finish()
}
}
}
}
impl core::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
ErrorKind::Adhoc(ref msg) => msg.fmt(f),
ErrorKind::Range(ref err) => err.fmt(f),
ErrorKind::FilePath(ref err) => err.fmt(f),
ErrorKind::IO(ref err) => err.fmt(f),
}
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
#[cfg(feature = "alloc")]
{
Error { inner: Arc::new(ErrorInner { kind, cause: None }) }
}
#[cfg(not(feature = "alloc"))]
{
Error { inner: Arc::new(ErrorInner { kind }) }
}
}
}
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
struct AdhocError {
#[cfg(feature = "alloc")]
message: alloc::boxed::Box<str>,
#[cfg(not(feature = "alloc"))]
message: &'static str,
}
impl AdhocError {
#[cfg(feature = "alloc")]
fn from_display<'a>(message: impl core::fmt::Display + 'a) -> AdhocError {
use alloc::string::ToString;
let message = message.to_string().into_boxed_str();
AdhocError { message }
}
fn from_args<'a>(message: core::fmt::Arguments<'a>) -> AdhocError {
#[cfg(feature = "alloc")]
{
AdhocError::from_display(message)
}
#[cfg(not(feature = "alloc"))]
{
let message = message.as_str().unwrap_or(
"unknown Jiff error (better error messages require \
enabling the `alloc` feature for the `jiff` crate)",
);
AdhocError::from_static_str(message)
}
}
fn from_static_str(message: &'static str) -> AdhocError {
#[cfg(feature = "alloc")]
{
AdhocError::from_display(message)
}
#[cfg(not(feature = "alloc"))]
{
AdhocError { message }
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for AdhocError {}
impl core::fmt::Display for AdhocError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(&self.message, f)
}
}
impl core::fmt::Debug for AdhocError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.message, f)
}
}
#[derive(Debug)]
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
struct RangeError {
what: &'static str,
#[cfg(feature = "alloc")]
given: i128,
#[cfg(feature = "alloc")]
min: i128,
#[cfg(feature = "alloc")]
max: i128,
}
impl RangeError {
fn new(
what: &'static str,
_given: impl Into<i128>,
_min: impl Into<i128>,
_max: impl Into<i128>,
) -> RangeError {
RangeError {
what,
#[cfg(feature = "alloc")]
given: _given.into(),
#[cfg(feature = "alloc")]
min: _min.into(),
#[cfg(feature = "alloc")]
max: _max.into(),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for RangeError {}
impl core::fmt::Display for RangeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "alloc")]
{
let RangeError { what, given, min, max } = *self;
write!(
f,
"parameter '{what}' with value {given} \
is not in the required range of {min}..={max}",
)
}
#[cfg(not(feature = "alloc"))]
{
let RangeError { what } = *self;
write!(f, "parameter '{what}' is not in the required range")
}
}
}
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
struct IOError {
#[cfg(feature = "std")]
err: std::io::Error,
}
#[cfg(feature = "std")]
impl std::error::Error for IOError {}
impl core::fmt::Display for IOError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "std")]
{
write!(f, "{}", self.err)
}
#[cfg(not(feature = "std"))]
{
write!(f, "<BUG: SHOULD NOT EXIST>")
}
}
}
impl core::fmt::Debug for IOError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "std")]
{
f.debug_struct("IOError").field("err", &self.err).finish()
}
#[cfg(not(feature = "std"))]
{
write!(f, "<BUG: SHOULD NOT EXIST>")
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for IOError {
fn from(err: std::io::Error) -> IOError {
IOError { err }
}
}
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
struct FilePathError {
#[cfg(feature = "std")]
path: std::path::PathBuf,
}
#[cfg(feature = "std")]
impl std::error::Error for FilePathError {}
impl core::fmt::Display for FilePathError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "std")]
{
write!(f, "{}", self.path.display())
}
#[cfg(not(feature = "std"))]
{
write!(f, "<BUG: SHOULD NOT EXIST>")
}
}
}
impl core::fmt::Debug for FilePathError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[cfg(feature = "std")]
{
f.debug_struct("FilePathError").field("path", &self.path).finish()
}
#[cfg(not(feature = "std"))]
{
write!(f, "<BUG: SHOULD NOT EXIST>")
}
}
}
pub(crate) trait IntoError {
fn into_error(self) -> Error;
}
impl IntoError for Error {
fn into_error(self) -> Error {
self
}
}
impl IntoError for &'static str {
fn into_error(self) -> Error {
Error::adhoc_from_static_str(self)
}
}
#[cfg(feature = "alloc")]
impl IntoError for alloc::string::String {
fn into_error(self) -> Error {
Error::adhoc(self)
}
}
pub(crate) trait ErrorContext {
fn context(self, consequent: impl IntoError) -> Self;
fn with_context<E: IntoError>(
self,
consequent: impl FnOnce() -> E,
) -> Self;
}
impl ErrorContext for Error {
fn context(self, consequent: impl IntoError) -> Error {
#[cfg(feature = "alloc")]
{
let mut err = consequent.into_error();
assert!(
err.inner.cause.is_none(),
"cause of consequence must be `None`"
);
Arc::get_mut(&mut err.inner).unwrap().cause = Some(self);
err
}
#[cfg(not(feature = "alloc"))]
{
consequent.into_error()
}
}
fn with_context<E: IntoError>(
self,
consequent: impl FnOnce() -> E,
) -> Error {
#[cfg(feature = "alloc")]
{
let mut err = consequent().into_error();
assert!(
err.inner.cause.is_none(),
"cause of consequence must be `None`"
);
Arc::get_mut(&mut err.inner).unwrap().cause = Some(self);
err
}
#[cfg(not(feature = "alloc"))]
{
consequent().into_error()
}
}
}
impl<T> ErrorContext for Result<T, Error> {
fn context(self, consequent: impl IntoError) -> Result<T, Error> {
self.map_err(|err| err.context(consequent))
}
fn with_context<E: IntoError>(
self,
consequent: impl FnOnce() -> E,
) -> Result<T, Error> {
self.map_err(|err| err.with_context(consequent))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_size() {
let mut expected_size = core::mem::size_of::<usize>();
if !cfg!(feature = "alloc") {
expected_size *= 3;
}
assert_eq!(expected_size, core::mem::size_of::<Error>());
}
}