use crate::util::{
b::{BoundsError, SpecialBoundsError},
sync::Arc,
};
pub(crate) mod civil;
pub(crate) mod duration;
pub(crate) mod fmt;
pub(crate) mod signed_duration;
pub(crate) mod span;
pub(crate) mod timestamp;
pub(crate) mod tz;
pub(crate) mod unit;
pub(crate) mod util;
pub(crate) mod zoned;
#[derive(Clone)]
pub struct Error {
inner: Option<Arc<ErrorInner>>,
}
#[derive(Debug)]
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
struct ErrorInner {
kind: ErrorKind,
#[cfg(feature = "alloc")]
cause: Option<Error>,
}
impl Error {
pub fn from_args<'a>(message: core::fmt::Arguments<'a>) -> Error {
Error::from(ErrorKind::Adhoc(AdhocError::from_args(message)))
}
pub fn is_range(&self) -> bool {
use self::ErrorKind::*;
matches!(
*self.root().kind(),
Bounds(_) | SpecialBounds(_) | ITimeRange(_)
)
}
pub fn is_invalid_parameter(&self) -> bool {
use self::civil::Error as CivilError;
use self::ErrorKind::*;
matches!(
*self.root().kind(),
UnitConfig(_)
| Civil(
CivilError::IllegalTimeWithMicrosecond
| CivilError::IllegalTimeWithMillisecond
| CivilError::IllegalTimeWithNanosecond
)
)
}
pub fn is_crate_feature(&self) -> bool {
matches!(*self.root().kind(), ErrorKind::CrateFeature(_))
}
}
impl Error {
#[inline(never)]
#[cold]
pub(crate) fn bounds(err: BoundsError) -> Error {
Error::from(ErrorKind::Bounds(err))
}
#[inline(never)]
#[cold]
pub(crate) fn special_bounds(err: SpecialBoundsError) -> Error {
Error::from(ErrorKind::SpecialBounds(err))
}
pub(crate) fn itime_range(
err: crate::shared::util::itime::RangeError,
) -> Error {
Error::from(ErrorKind::ITimeRange(err))
}
#[cfg(feature = "alloc")]
pub(crate) fn tzif(err: crate::shared::tzif::TzifError) -> Error {
Error::from(ErrorKind::Tzif(err))
}
pub(crate) fn posix_tz(
err: crate::shared::posix::PosixTimeZoneError,
) -> Error {
Error::from(ErrorKind::PosixTz(err))
}
#[cfg(feature = "std")]
#[inline(never)]
#[cold]
pub(crate) fn io(err: std::io::Error) -> Error {
Error::from(ErrorKind::IO(IOError { err }))
}
#[cfg(any(feature = "tzdb-zoneinfo", feature = "tzdb-concatenated"))]
#[inline(never)]
#[cold]
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_attr(feature = "perf-inline", inline(always))]
pub(crate) fn context(self, consequent: impl IntoError) -> Error {
self.context_impl(consequent.into_error())
}
#[inline(never)]
#[cold]
fn context_impl(self, _consequent: Error) -> Error {
#[cfg(feature = "alloc")]
{
let mut err = _consequent;
if err.inner.is_none() {
err = Error::from(ErrorKind::Unknown);
}
let inner = err.inner.as_mut().unwrap();
assert!(
inner.cause.is_none(),
"cause of consequence must be `None`"
);
Arc::get_mut(inner).unwrap().cause = Some(self);
err
}
#[cfg(not(feature = "alloc"))]
{
self
}
}
fn root(&self) -> &Error {
self.chain().last().unwrap()
}
fn chain(&self) -> impl Iterator<Item = &Error> {
#[cfg(feature = "alloc")]
{
let mut err = self;
core::iter::once(err).chain(core::iter::from_fn(move || {
err = err
.inner
.as_ref()
.and_then(|inner| inner.cause.as_ref())?;
Some(err)
}))
}
#[cfg(not(feature = "alloc"))]
{
core::iter::once(self)
}
}
fn kind(&self) -> &ErrorKind {
self.inner
.as_ref()
.map(|inner| &inner.kind)
.unwrap_or(&ErrorKind::Unknown)
}
}
#[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 {
let mut it = self.chain().peekable();
while let Some(err) = it.next() {
core::fmt::Display::fmt(err.kind(), f)?;
if it.peek().is_some() {
f.write_str(": ")?;
}
}
Ok(())
}
}
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 {
let Some(ref inner) = self.inner else {
return f
.debug_struct("Error")
.field("kind", &"None")
.finish();
};
#[cfg(feature = "alloc")]
{
f.debug_struct("Error")
.field("kind", &inner.kind)
.field("cause", &inner.cause)
.finish()
}
#[cfg(not(feature = "alloc"))]
{
f.debug_struct("Error").field("kind", &inner.kind).finish()
}
}
}
}
#[derive(Debug)]
#[cfg_attr(not(feature = "alloc"), derive(Clone))]
enum ErrorKind {
Adhoc(AdhocError),
Bounds(BoundsError),
Civil(self::civil::Error),
CrateFeature(CrateFeatureError),
Duration(self::duration::Error),
#[allow(dead_code)] FilePath(FilePathError),
Fmt(self::fmt::Error),
FmtFriendly(self::fmt::friendly::Error),
FmtOffset(self::fmt::offset::Error),
FmtRfc2822(self::fmt::rfc2822::Error),
FmtRfc9557(self::fmt::rfc9557::Error),
FmtTemporal(self::fmt::temporal::Error),
FmtUtil(self::fmt::util::Error),
FmtStrtime(self::fmt::strtime::Error),
FmtStrtimeFormat(self::fmt::strtime::FormatError),
FmtStrtimeParse(self::fmt::strtime::ParseError),
#[allow(dead_code)] IO(IOError),
ITimeRange(crate::shared::util::itime::RangeError),
OsStrUtf8(self::util::OsStrUtf8Error),
ParseInt(self::util::ParseIntError),
ParseFraction(self::util::ParseFractionError),
PosixTz(crate::shared::posix::PosixTimeZoneError),
RoundingIncrement(self::util::RoundingIncrementError),
SignedDuration(self::signed_duration::Error),
Span(self::span::Error),
UnitConfig(self::unit::UnitConfigError),
SpecialBounds(SpecialBoundsError),
Timestamp(self::timestamp::Error),
TzAmbiguous(self::tz::ambiguous::Error),
TzDb(self::tz::db::Error),
TzConcatenated(self::tz::concatenated::Error),
TzOffset(self::tz::offset::Error),
TzPosix(self::tz::posix::Error),
TzSystem(self::tz::system::Error),
TzTimeZone(self::tz::timezone::Error),
#[allow(dead_code)]
TzZic(self::tz::zic::Error),
#[cfg(feature = "alloc")]
Tzif(crate::shared::tzif::TzifError),
Unknown,
Zoned(self::zoned::Error),
}
impl core::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::ErrorKind::*;
match *self {
Adhoc(ref msg) => msg.fmt(f),
Bounds(ref msg) => msg.fmt(f),
Civil(ref err) => err.fmt(f),
CrateFeature(ref err) => err.fmt(f),
Duration(ref err) => err.fmt(f),
FilePath(ref err) => err.fmt(f),
Fmt(ref err) => err.fmt(f),
FmtFriendly(ref err) => err.fmt(f),
FmtOffset(ref err) => err.fmt(f),
FmtRfc2822(ref err) => err.fmt(f),
FmtRfc9557(ref err) => err.fmt(f),
FmtUtil(ref err) => err.fmt(f),
FmtStrtime(ref err) => err.fmt(f),
FmtStrtimeFormat(ref err) => err.fmt(f),
FmtStrtimeParse(ref err) => err.fmt(f),
FmtTemporal(ref err) => err.fmt(f),
IO(ref err) => err.fmt(f),
ITimeRange(ref err) => err.fmt(f),
OsStrUtf8(ref err) => err.fmt(f),
ParseInt(ref err) => err.fmt(f),
ParseFraction(ref err) => err.fmt(f),
PosixTz(ref err) => err.fmt(f),
RoundingIncrement(ref err) => err.fmt(f),
SignedDuration(ref err) => err.fmt(f),
Span(ref err) => err.fmt(f),
UnitConfig(ref err) => err.fmt(f),
SpecialBounds(ref msg) => msg.fmt(f),
Timestamp(ref err) => err.fmt(f),
TzAmbiguous(ref err) => err.fmt(f),
TzDb(ref err) => err.fmt(f),
TzConcatenated(ref err) => err.fmt(f),
TzOffset(ref err) => err.fmt(f),
TzPosix(ref err) => err.fmt(f),
TzSystem(ref err) => err.fmt(f),
TzTimeZone(ref err) => err.fmt(f),
TzZic(ref err) => err.fmt(f),
#[cfg(feature = "alloc")]
Tzif(ref err) => err.fmt(f),
Unknown => f.write_str("unknown Jiff error"),
Zoned(ref err) => err.fmt(f),
}
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
#[cfg(feature = "alloc")]
{
Error { inner: Some(Arc::new(ErrorInner { kind, cause: None })) }
}
#[cfg(not(feature = "alloc"))]
{
Error { inner: Some(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 {
fn from_args<'a>(message: core::fmt::Arguments<'a>) -> AdhocError {
#[cfg(feature = "alloc")]
{
use alloc::string::ToString;
let message = message.to_string().into_boxed_str();
AdhocError { 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 { 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(Clone, Debug)]
pub(crate) enum CrateFeatureError {
#[cfg(not(feature = "tz-system"))]
TzSystem,
#[cfg(not(feature = "tzdb-concatenated"))]
TzdbConcatenated,
#[cfg(not(feature = "tzdb-zoneinfo"))]
TzdbZoneInfo,
}
impl From<CrateFeatureError> for Error {
#[cold]
#[inline(never)]
fn from(err: CrateFeatureError) -> Error {
ErrorKind::CrateFeature(err).into()
}
}
impl core::fmt::Display for CrateFeatureError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
#[allow(unused_imports)]
use self::CrateFeatureError::*;
f.write_str("operation failed because Jiff crate feature `")?;
#[allow(unused_variables)]
let name: &str = match *self {
#[cfg(not(feature = "tz-system"))]
TzSystem => "tz-system",
#[cfg(not(feature = "tzdb-concatenated"))]
TzdbConcatenated => "tzdb-concatenated",
#[cfg(not(feature = "tzdb-zoneinfo"))]
TzdbZoneInfo => "tzdb-zoneinfo",
};
#[allow(unreachable_code)]
{
core::fmt::Display::fmt(name, f)?;
f.write_str("` is not enabled")
}
}
}
#[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")]
{
self.err.fmt(f)
}
#[cfg(not(feature = "std"))]
{
f.write_str("<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"))]
{
f.write_str("<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")]
{
self.path.display().fmt(f)
}
#[cfg(not(feature = "std"))]
{
f.write_str("<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"))]
{
f.write_str("<BUG: SHOULD NOT EXIST>")
}
}
}
pub(crate) trait IntoError {
fn into_error(self) -> Error;
}
impl IntoError for Error {
#[inline(always)]
fn into_error(self) -> Error {
self
}
}
pub(crate) trait ErrorContext<T, E> {
fn context(self, consequent: impl IntoError) -> Result<T, Error>;
fn with_context<C: IntoError>(
self,
consequent: impl FnOnce() -> C,
) -> Result<T, Error>;
}
impl<T, E> ErrorContext<T, E> for Result<T, E>
where
E: IntoError,
{
#[cfg_attr(feature = "perf-inline", inline(always))]
fn context(self, consequent: impl IntoError) -> Result<T, Error> {
self.map_err(|err| {
err.into_error().context_impl(consequent.into_error())
})
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn with_context<C: IntoError>(
self,
consequent: impl FnOnce() -> C,
) -> Result<T, Error> {
self.map_err(|err| {
err.into_error().context_impl(consequent().into_error())
})
}
}
#[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 *= 4;
}
assert_eq!(expected_size, core::mem::size_of::<Error>());
}
}