Skip to main content

jiff/error/
mod.rs

1use crate::util::sync::Arc;
2
3pub(crate) mod civil;
4pub(crate) mod duration;
5pub(crate) mod fmt;
6pub(crate) mod signed_duration;
7pub(crate) mod span;
8pub(crate) mod timestamp;
9pub(crate) mod tz;
10pub(crate) mod util;
11pub(crate) mod zoned;
12
13/// An error that can occur in this crate.
14///
15/// The most common type of error is a result of overflow. But other errors
16/// exist as well:
17///
18/// * Time zone database lookup failure.
19/// * Configuration problem. (For example, trying to round a span with calendar
20/// units without providing a relative datetime.)
21/// * An I/O error as a result of trying to open a time zone database from a
22/// directory via
23/// [`TimeZoneDatabase::from_dir`](crate::tz::TimeZoneDatabase::from_dir).
24/// * Parse errors.
25///
26/// # Introspection is limited
27///
28/// Other than implementing the [`std::error::Error`] trait when the
29/// `std` feature is enabled, the [`core::fmt::Debug`] trait and the
30/// [`core::fmt::Display`] trait, this error type currently provides
31/// very limited introspection capabilities. Simple predicates like
32/// `Error::is_range` are provided, but the predicates are not
33/// exhaustive. That is, there exist some errors that do not return
34/// `true` for any of the `Error::is_*` predicates.
35///
36/// # Design
37///
38/// This crate follows the "One True God Error Type Pattern," where only one
39/// error type exists for a variety of different operations. This design was
40/// chosen after attempting to provide finer grained error types. But finer
41/// grained error types proved difficult in the face of composition.
42///
43/// More about this design choice can be found in a GitHub issue
44/// [about error types].
45///
46/// [about error types]: https://github.com/BurntSushi/jiff/issues/8
47#[derive(Clone)]
48pub struct Error {
49    /// The internal representation of an error.
50    ///
51    /// This is in an `Arc` to make an `Error` cloneable. It could otherwise
52    /// be automatically cloneable, but it embeds a `std::io::Error` when the
53    /// `std` feature is enabled, which isn't cloneable.
54    ///
55    /// This also makes clones cheap. And it also make the size of error equal
56    /// to one word (although a `Box` would achieve that last goal). This is
57    /// why we put the `Arc` here instead of on `std::io::Error` directly.
58    inner: Option<Arc<ErrorInner>>,
59}
60
61#[derive(Debug)]
62#[cfg_attr(not(feature = "alloc"), derive(Clone))]
63struct ErrorInner {
64    kind: ErrorKind,
65    #[cfg(feature = "alloc")]
66    cause: Option<Error>,
67}
68
69impl Error {
70    /// Creates a new error value from `core::fmt::Arguments`.
71    ///
72    /// It is expected to use [`format_args!`](format_args) from
73    /// Rust's standard library (available in `core`) to create a
74    /// `core::fmt::Arguments`.
75    ///
76    /// Callers should generally use their own error types. But in some
77    /// circumstances, it can be convenient to manufacture a Jiff error value
78    /// specifically.
79    ///
80    /// # Core-only environments
81    ///
82    /// In core-only environments without a dynamic memory allocator, error
83    /// messages may be degraded in some cases. For example, if the given
84    /// `core::fmt::Arguments` could not be converted to a simple borrowed
85    /// `&str`, then this will ignore the input given and return an "unknown"
86    /// Jiff error.
87    ///
88    /// # Example
89    ///
90    /// ```
91    /// use jiff::Error;
92    ///
93    /// let err = Error::from_args(format_args!("something failed"));
94    /// assert_eq!(err.to_string(), "something failed");
95    /// ```
96    pub fn from_args<'a>(message: core::fmt::Arguments<'a>) -> Error {
97        Error::from(ErrorKind::Adhoc(AdhocError::from_args(message)))
98    }
99
100    /// Returns true when this error originated as a result of a value being
101    /// out of Jiff's supported range.
102    ///
103    /// # Example
104    ///
105    /// ```
106    /// use jiff::civil::Date;
107    ///
108    /// assert!(Date::new(2025, 2, 29).unwrap_err().is_range());
109    /// assert!("2025-02-29".parse::<Date>().unwrap_err().is_range());
110    /// assert!(Date::strptime("%Y-%m-%d", "2025-02-29").unwrap_err().is_range());
111    /// ```
112    pub fn is_range(&self) -> bool {
113        use self::ErrorKind::*;
114        matches!(*self.root().kind(), Range(_) | SlimRange(_) | ITimeRange(_))
115    }
116
117    /// Returns true when this error originated as a result of an invalid
118    /// configuration of parameters to a function call.
119    ///
120    /// This particular error category is somewhat nebulous, but it's generally
121    /// meant to cover errors that _could_ have been statically prevented by
122    /// Jiff with more types in its API. Instead, a smaller API is preferred.
123    ///
124    /// # Example: invalid rounding options
125    ///
126    /// ```
127    /// use jiff::{SpanRound, ToSpan, Unit};
128    ///
129    /// let span = 44.seconds();
130    /// let err = span.round(
131    ///     SpanRound::new().smallest(Unit::Second).increment(45),
132    /// ).unwrap_err();
133    /// // Rounding increments for seconds must divide evenly into `60`.
134    /// // But `45` does not. Thus, this is a "configuration" error.
135    /// assert!(err.is_invalid_parameter());
136    /// ```
137    ///
138    /// # Example: invalid units
139    ///
140    /// One cannot round a span between dates to units less than days:
141    ///
142    /// ```
143    /// use jiff::{civil::date, Unit};
144    ///
145    /// let date1 = date(2025, 3, 18);
146    /// let date2 = date(2025, 12, 21);
147    /// let err = date1.until((Unit::Hour, date2)).unwrap_err();
148    /// assert!(err.is_invalid_parameter());
149    /// ```
150    ///
151    /// Similarly, one cannot round a span between times to units greater than
152    /// hours:
153    ///
154    /// ```
155    /// use jiff::{civil::time, Unit};
156    ///
157    /// let time1 = time(9, 39, 0, 0);
158    /// let time2 = time(17, 0, 0, 0);
159    /// let err = time1.until((Unit::Day, time2)).unwrap_err();
160    /// assert!(err.is_invalid_parameter());
161    /// ```
162    pub fn is_invalid_parameter(&self) -> bool {
163        use self::ErrorKind::*;
164        use self::{
165            civil::Error as CivilError, span::Error as SpanError,
166            tz::offset::Error as OffsetError, util::RoundingIncrementError,
167        };
168
169        matches!(
170            *self.root().kind(),
171            RoundingIncrement(
172                RoundingIncrementError::GreaterThanZero { .. }
173                    | RoundingIncrementError::InvalidDivide { .. }
174                    | RoundingIncrementError::Unsupported { .. }
175            ) | Span(
176                SpanError::NotAllowedCalendarUnits { .. }
177                    | SpanError::NotAllowedLargestSmallerThanSmallest { .. }
178                    | SpanError::RequiresRelativeWeekOrDay { .. }
179                    | SpanError::RequiresRelativeYearOrMonth { .. }
180                    | SpanError::RequiresRelativeYearOrMonthGivenDaysAre24Hours { .. }
181            ) | Civil(
182                CivilError::IllegalTimeWithMicrosecond
183                | CivilError::IllegalTimeWithMillisecond
184                | CivilError::IllegalTimeWithNanosecond
185                | CivilError::RoundMustUseDaysOrBigger { .. }
186                | CivilError::RoundMustUseHoursOrSmaller { .. }
187            ) | TzOffset(OffsetError::RoundInvalidUnit { .. })
188        )
189    }
190
191    /// Returns true when this error originated as a result of an operation
192    /// failing because an appropriate Jiff crate feature was not enabled.
193    ///
194    /// # Example
195    ///
196    /// ```ignore
197    /// use jiff::tz::TimeZone;
198    ///
199    /// // This passes when the `tz-system` crate feature is NOT enabled.
200    /// assert!(TimeZone::try_system().unwrap_err().is_crate_feature());
201    /// ```
202    pub fn is_crate_feature(&self) -> bool {
203        matches!(*self.root().kind(), ErrorKind::CrateFeature(_))
204    }
205}
206
207impl Error {
208    /// Creates a new error indicating that a `given` value is out of the
209    /// specified `min..=max` range. The given `what` label is used in the
210    /// error message as a human readable description of what exactly is out
211    /// of range. (e.g., "seconds")
212    #[inline(never)]
213    #[cold]
214    pub(crate) fn range(
215        what: &'static str,
216        given: impl Into<i128>,
217        min: impl Into<i128>,
218        max: impl Into<i128>,
219    ) -> Error {
220        Error::from(ErrorKind::Range(RangeError::new(what, given, min, max)))
221    }
222
223    /// Creates a new error indicating that a `given` value is out of the
224    /// allowed range.
225    ///
226    /// This is similar to `Error::range`, but the error message doesn't
227    /// include the illegal value or the allowed range. This is useful for
228    /// ad hoc range errors but should generally be used sparingly.
229    #[inline(never)]
230    #[cold]
231    pub(crate) fn slim_range(what: &'static str) -> Error {
232        Error::from(ErrorKind::SlimRange(SlimRangeError::new(what)))
233    }
234
235    /// Creates a new error from the special "shared" error type.
236    pub(crate) fn itime_range(
237        err: crate::shared::util::itime::RangeError,
238    ) -> Error {
239        Error::from(ErrorKind::ITimeRange(err))
240    }
241
242    /// Creates a new error from the special TZif error type.
243    #[cfg(feature = "alloc")]
244    pub(crate) fn tzif(err: crate::shared::tzif::TzifError) -> Error {
245        Error::from(ErrorKind::Tzif(err))
246    }
247
248    /// Creates a new error from the special `PosixTimeZoneError` type.
249    pub(crate) fn posix_tz(
250        err: crate::shared::posix::PosixTimeZoneError,
251    ) -> Error {
252        Error::from(ErrorKind::PosixTz(err))
253    }
254
255    /// A convenience constructor for building an I/O error.
256    ///
257    /// This returns an error that is just a simple wrapper around the
258    /// `std::io::Error` type. In general, callers should always attach some
259    /// kind of context to this error (like a file path).
260    ///
261    /// This is only available when the `std` feature is enabled.
262    #[cfg(feature = "std")]
263    #[inline(never)]
264    #[cold]
265    pub(crate) fn io(err: std::io::Error) -> Error {
266        Error::from(ErrorKind::IO(IOError { err }))
267    }
268
269    /// Contextualizes this error by associating the given file path with it.
270    ///
271    /// This is a convenience routine for calling `Error::context` with a
272    /// `FilePathError`.
273    #[cfg(any(feature = "tzdb-zoneinfo", feature = "tzdb-concatenated"))]
274    #[inline(never)]
275    #[cold]
276    pub(crate) fn path(self, path: impl Into<std::path::PathBuf>) -> Error {
277        let err = Error::from(ErrorKind::FilePath(FilePathError {
278            path: path.into(),
279        }));
280        self.context(err)
281    }
282
283    /*
284    /// Creates a new "unknown" Jiff error.
285    ///
286    /// The benefit of this API is that it permits creating an `Error` in a
287    /// `const` context. But the error message quality is currently pretty
288    /// bad: it's just a generic "unknown Jiff error" message.
289    ///
290    /// This could be improved to take a `&'static str`, but I believe this
291    /// will require pointer tagging in order to avoid increasing the size of
292    /// `Error`. (Which is important, because of how many perf sensitive
293    /// APIs return a `Result<T, Error>` in Jiff.
294    pub(crate) const fn unknown() -> Error {
295        Error { inner: None }
296    }
297    */
298
299    #[cfg_attr(feature = "perf-inline", inline(always))]
300    pub(crate) fn context(self, consequent: impl IntoError) -> Error {
301        self.context_impl(consequent.into_error())
302    }
303
304    #[inline(never)]
305    #[cold]
306    fn context_impl(self, _consequent: Error) -> Error {
307        #[cfg(feature = "alloc")]
308        {
309            let mut err = _consequent;
310            if err.inner.is_none() {
311                err = Error::from(ErrorKind::Unknown);
312            }
313            let inner = err.inner.as_mut().unwrap();
314            assert!(
315                inner.cause.is_none(),
316                "cause of consequence must be `None`"
317            );
318            // OK because we just created this error so the Arc
319            // has one reference.
320            Arc::get_mut(inner).unwrap().cause = Some(self);
321            err
322        }
323        #[cfg(not(feature = "alloc"))]
324        {
325            // We just completely drop `self`. :-(
326            //
327            // 2025-12-21: ... actually, we used to drop self, but this
328            // ends up dropping the root cause. And the root cause
329            // is how the predicates on `Error` work. So we drop the
330            // consequent instead.
331            self
332        }
333    }
334
335    /// Returns the root error in this chain.
336    fn root(&self) -> &Error {
337        // OK because `Error::chain` is guaranteed to return a non-empty
338        // iterator.
339        self.chain().last().unwrap()
340    }
341
342    /// Returns a chain of error values.
343    ///
344    /// This starts with the most recent error added to the chain. That is,
345    /// the highest level context. The last error in the chain is always the
346    /// "root" cause. That is, the error closest to the point where something
347    /// has gone wrong.
348    ///
349    /// The iterator returned is guaranteed to yield at least one error.
350    fn chain(&self) -> impl Iterator<Item = &Error> {
351        #[cfg(feature = "alloc")]
352        {
353            let mut err = self;
354            core::iter::once(err).chain(core::iter::from_fn(move || {
355                err = err
356                    .inner
357                    .as_ref()
358                    .and_then(|inner| inner.cause.as_ref())?;
359                Some(err)
360            }))
361        }
362        #[cfg(not(feature = "alloc"))]
363        {
364            core::iter::once(self)
365        }
366    }
367
368    /// Returns the kind of this error.
369    fn kind(&self) -> &ErrorKind {
370        self.inner
371            .as_ref()
372            .map(|inner| &inner.kind)
373            .unwrap_or(&ErrorKind::Unknown)
374    }
375}
376
377#[cfg(feature = "std")]
378impl std::error::Error for Error {}
379
380impl core::fmt::Display for Error {
381    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
382        let mut it = self.chain().peekable();
383        while let Some(err) = it.next() {
384            core::fmt::Display::fmt(err.kind(), f)?;
385            if it.peek().is_some() {
386                f.write_str(": ")?;
387            }
388        }
389        Ok(())
390    }
391}
392
393impl core::fmt::Debug for Error {
394    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
395        if !f.alternate() {
396            core::fmt::Display::fmt(self, f)
397        } else {
398            let Some(ref inner) = self.inner else {
399                return f
400                    .debug_struct("Error")
401                    .field("kind", &"None")
402                    .finish();
403            };
404            #[cfg(feature = "alloc")]
405            {
406                f.debug_struct("Error")
407                    .field("kind", &inner.kind)
408                    .field("cause", &inner.cause)
409                    .finish()
410            }
411            #[cfg(not(feature = "alloc"))]
412            {
413                f.debug_struct("Error").field("kind", &inner.kind).finish()
414            }
415        }
416    }
417}
418
419/// The underlying kind of a [`Error`].
420#[derive(Debug)]
421#[cfg_attr(not(feature = "alloc"), derive(Clone))]
422enum ErrorKind {
423    Adhoc(AdhocError),
424    Civil(self::civil::Error),
425    CrateFeature(CrateFeatureError),
426    Duration(self::duration::Error),
427    #[allow(dead_code)] // not used in some feature configs
428    FilePath(FilePathError),
429    Fmt(self::fmt::Error),
430    FmtFriendly(self::fmt::friendly::Error),
431    FmtOffset(self::fmt::offset::Error),
432    FmtRfc2822(self::fmt::rfc2822::Error),
433    FmtRfc9557(self::fmt::rfc9557::Error),
434    FmtTemporal(self::fmt::temporal::Error),
435    FmtUtil(self::fmt::util::Error),
436    FmtStrtime(self::fmt::strtime::Error),
437    FmtStrtimeFormat(self::fmt::strtime::FormatError),
438    FmtStrtimeParse(self::fmt::strtime::ParseError),
439    #[allow(dead_code)] // not used in some feature configs
440    IO(IOError),
441    ITimeRange(crate::shared::util::itime::RangeError),
442    OsStrUtf8(self::util::OsStrUtf8Error),
443    ParseInt(self::util::ParseIntError),
444    ParseFraction(self::util::ParseFractionError),
445    PosixTz(crate::shared::posix::PosixTimeZoneError),
446    Range(RangeError),
447    RoundingIncrement(self::util::RoundingIncrementError),
448    SignedDuration(self::signed_duration::Error),
449    SlimRange(SlimRangeError),
450    Span(self::span::Error),
451    Timestamp(self::timestamp::Error),
452    TzAmbiguous(self::tz::ambiguous::Error),
453    TzDb(self::tz::db::Error),
454    TzConcatenated(self::tz::concatenated::Error),
455    TzOffset(self::tz::offset::Error),
456    TzPosix(self::tz::posix::Error),
457    TzSystem(self::tz::system::Error),
458    TzTimeZone(self::tz::timezone::Error),
459    #[allow(dead_code)]
460    TzZic(self::tz::zic::Error),
461    #[cfg(feature = "alloc")]
462    Tzif(crate::shared::tzif::TzifError),
463    Unknown,
464    Zoned(self::zoned::Error),
465}
466
467impl core::fmt::Display for ErrorKind {
468    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
469        use self::ErrorKind::*;
470
471        match *self {
472            Adhoc(ref msg) => msg.fmt(f),
473            Civil(ref err) => err.fmt(f),
474            CrateFeature(ref err) => err.fmt(f),
475            Duration(ref err) => err.fmt(f),
476            FilePath(ref err) => err.fmt(f),
477            Fmt(ref err) => err.fmt(f),
478            FmtFriendly(ref err) => err.fmt(f),
479            FmtOffset(ref err) => err.fmt(f),
480            FmtRfc2822(ref err) => err.fmt(f),
481            FmtRfc9557(ref err) => err.fmt(f),
482            FmtUtil(ref err) => err.fmt(f),
483            FmtStrtime(ref err) => err.fmt(f),
484            FmtStrtimeFormat(ref err) => err.fmt(f),
485            FmtStrtimeParse(ref err) => err.fmt(f),
486            FmtTemporal(ref err) => err.fmt(f),
487            IO(ref err) => err.fmt(f),
488            ITimeRange(ref err) => err.fmt(f),
489            OsStrUtf8(ref err) => err.fmt(f),
490            ParseInt(ref err) => err.fmt(f),
491            ParseFraction(ref err) => err.fmt(f),
492            PosixTz(ref err) => err.fmt(f),
493            Range(ref err) => err.fmt(f),
494            RoundingIncrement(ref err) => err.fmt(f),
495            SignedDuration(ref err) => err.fmt(f),
496            SlimRange(ref err) => err.fmt(f),
497            Span(ref err) => err.fmt(f),
498            Timestamp(ref err) => err.fmt(f),
499            TzAmbiguous(ref err) => err.fmt(f),
500            TzDb(ref err) => err.fmt(f),
501            TzConcatenated(ref err) => err.fmt(f),
502            TzOffset(ref err) => err.fmt(f),
503            TzPosix(ref err) => err.fmt(f),
504            TzSystem(ref err) => err.fmt(f),
505            TzTimeZone(ref err) => err.fmt(f),
506            TzZic(ref err) => err.fmt(f),
507            #[cfg(feature = "alloc")]
508            Tzif(ref err) => err.fmt(f),
509            Unknown => f.write_str("unknown Jiff error"),
510            Zoned(ref err) => err.fmt(f),
511        }
512    }
513}
514
515impl From<ErrorKind> for Error {
516    fn from(kind: ErrorKind) -> Error {
517        #[cfg(feature = "alloc")]
518        {
519            Error { inner: Some(Arc::new(ErrorInner { kind, cause: None })) }
520        }
521        #[cfg(not(feature = "alloc"))]
522        {
523            Error { inner: Some(Arc::new(ErrorInner { kind })) }
524        }
525    }
526}
527
528/// A generic error message.
529///
530/// This used to be used to represent most errors in Jiff. But then I switched
531/// to more structured error types (internally). We still keep this around to
532/// support the `Error::from_args` public API, which permits users of Jiff to
533/// manifest their own `Error` values from an arbitrary message.
534#[cfg_attr(not(feature = "alloc"), derive(Clone))]
535struct AdhocError {
536    #[cfg(feature = "alloc")]
537    message: alloc::boxed::Box<str>,
538    #[cfg(not(feature = "alloc"))]
539    message: &'static str,
540}
541
542impl AdhocError {
543    fn from_args<'a>(message: core::fmt::Arguments<'a>) -> AdhocError {
544        #[cfg(feature = "alloc")]
545        {
546            use alloc::string::ToString;
547
548            let message = message.to_string().into_boxed_str();
549            AdhocError { message }
550        }
551        #[cfg(not(feature = "alloc"))]
552        {
553            let message = message.as_str().unwrap_or(
554                "unknown Jiff error (better error messages require \
555                 enabling the `alloc` feature for the `jiff` crate)",
556            );
557            AdhocError { message }
558        }
559    }
560}
561
562#[cfg(feature = "std")]
563impl std::error::Error for AdhocError {}
564
565impl core::fmt::Display for AdhocError {
566    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
567        core::fmt::Display::fmt(&self.message, f)
568    }
569}
570
571impl core::fmt::Debug for AdhocError {
572    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
573        core::fmt::Debug::fmt(&self.message, f)
574    }
575}
576
577/// An error that occurs when an input value is out of bounds.
578///
579/// The error message produced by this type will include a name describing
580/// which input was out of bounds, the value given and its minimum and maximum
581/// allowed values.
582#[derive(Debug)]
583#[cfg_attr(not(feature = "alloc"), derive(Clone))]
584struct RangeError {
585    what: &'static str,
586    #[cfg(feature = "alloc")]
587    given: i128,
588    #[cfg(feature = "alloc")]
589    min: i128,
590    #[cfg(feature = "alloc")]
591    max: i128,
592}
593
594impl RangeError {
595    fn new(
596        what: &'static str,
597        _given: impl Into<i128>,
598        _min: impl Into<i128>,
599        _max: impl Into<i128>,
600    ) -> RangeError {
601        RangeError {
602            what,
603            #[cfg(feature = "alloc")]
604            given: _given.into(),
605            #[cfg(feature = "alloc")]
606            min: _min.into(),
607            #[cfg(feature = "alloc")]
608            max: _max.into(),
609        }
610    }
611}
612
613#[cfg(feature = "std")]
614impl std::error::Error for RangeError {}
615
616impl core::fmt::Display for RangeError {
617    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
618        #[cfg(feature = "alloc")]
619        {
620            let RangeError { what, given, min, max } = *self;
621            write!(
622                f,
623                "parameter '{what}' with value {given} \
624                 is not in the required range of {min}..={max}",
625            )
626        }
627        #[cfg(not(feature = "alloc"))]
628        {
629            let RangeError { what } = *self;
630            write!(f, "parameter '{what}' is not in the required range")
631        }
632    }
633}
634
635/// A slim error that occurs when an input value is out of bounds.
636///
637/// Unlike `RangeError`, this only includes a static description of the
638/// value that is out of bounds. It doesn't include the out-of-range value
639/// or the min/max values.
640#[derive(Clone, Debug)]
641struct SlimRangeError {
642    what: &'static str,
643}
644
645impl SlimRangeError {
646    fn new(what: &'static str) -> SlimRangeError {
647        SlimRangeError { what }
648    }
649}
650
651#[cfg(feature = "std")]
652impl std::error::Error for SlimRangeError {}
653
654impl core::fmt::Display for SlimRangeError {
655    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
656        let SlimRangeError { what } = *self;
657        write!(f, "parameter '{what}' is not in the required range")
658    }
659}
660
661/// An error used whenever a failure is caused by a missing crate feature.
662///
663/// This enum doesn't necessarily contain every Jiff crate feature. It only
664/// contains the features whose absence can result in an error.
665#[derive(Clone, Debug)]
666pub(crate) enum CrateFeatureError {
667    #[cfg(not(feature = "tz-system"))]
668    TzSystem,
669    #[cfg(not(feature = "tzdb-concatenated"))]
670    TzdbConcatenated,
671    #[cfg(not(feature = "tzdb-zoneinfo"))]
672    TzdbZoneInfo,
673}
674
675impl From<CrateFeatureError> for Error {
676    #[cold]
677    #[inline(never)]
678    fn from(err: CrateFeatureError) -> Error {
679        ErrorKind::CrateFeature(err).into()
680    }
681}
682
683impl core::fmt::Display for CrateFeatureError {
684    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
685        #[allow(unused_imports)]
686        use self::CrateFeatureError::*;
687
688        f.write_str("operation failed because Jiff crate feature `")?;
689        #[allow(unused_variables)]
690        let name: &str = match *self {
691            #[cfg(not(feature = "tz-system"))]
692            TzSystem => "tz-system",
693            #[cfg(not(feature = "tzdb-concatenated"))]
694            TzdbConcatenated => "tzdb-concatenated",
695            #[cfg(not(feature = "tzdb-zoneinfo"))]
696            TzdbZoneInfo => "tzdb-zoneinfo",
697        };
698        #[allow(unreachable_code)]
699        {
700            core::fmt::Display::fmt(name, f)?;
701            f.write_str("` is not enabled")
702        }
703    }
704}
705
706/// A `std::io::Error`.
707///
708/// This type is itself always available, even when the `std` feature is not
709/// enabled. When `std` is not enabled, a value of this type can never be
710/// constructed.
711///
712/// Otherwise, this type is a simple wrapper around `std::io::Error`. Its
713/// purpose is to encapsulate the conditional compilation based on the `std`
714/// feature.
715#[cfg_attr(not(feature = "alloc"), derive(Clone))]
716struct IOError {
717    #[cfg(feature = "std")]
718    err: std::io::Error,
719}
720
721#[cfg(feature = "std")]
722impl std::error::Error for IOError {}
723
724impl core::fmt::Display for IOError {
725    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
726        #[cfg(feature = "std")]
727        {
728            self.err.fmt(f)
729        }
730        #[cfg(not(feature = "std"))]
731        {
732            f.write_str("<BUG: SHOULD NOT EXIST>")
733        }
734    }
735}
736
737impl core::fmt::Debug for IOError {
738    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
739        #[cfg(feature = "std")]
740        {
741            f.debug_struct("IOError").field("err", &self.err).finish()
742        }
743        #[cfg(not(feature = "std"))]
744        {
745            f.write_str("<BUG: SHOULD NOT EXIST>")
746        }
747    }
748}
749
750#[cfg(feature = "std")]
751impl From<std::io::Error> for IOError {
752    fn from(err: std::io::Error) -> IOError {
753        IOError { err }
754    }
755}
756
757#[cfg_attr(not(feature = "alloc"), derive(Clone))]
758struct FilePathError {
759    #[cfg(feature = "std")]
760    path: std::path::PathBuf,
761}
762
763#[cfg(feature = "std")]
764impl std::error::Error for FilePathError {}
765
766impl core::fmt::Display for FilePathError {
767    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
768        #[cfg(feature = "std")]
769        {
770            self.path.display().fmt(f)
771        }
772        #[cfg(not(feature = "std"))]
773        {
774            f.write_str("<BUG: SHOULD NOT EXIST>")
775        }
776    }
777}
778
779impl core::fmt::Debug for FilePathError {
780    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
781        #[cfg(feature = "std")]
782        {
783            f.debug_struct("FilePathError").field("path", &self.path).finish()
784        }
785        #[cfg(not(feature = "std"))]
786        {
787            f.write_str("<BUG: SHOULD NOT EXIST>")
788        }
789    }
790}
791
792/// A simple trait to encapsulate automatic conversion to `Error`.
793///
794/// This trait basically exists to make `Error::context` work without needing
795/// to rely on public `From` impls. For example, without this trait, we might
796/// otherwise write `impl From<String> for Error`. But this would make it part
797/// of the public API. Which... maybe we should do, but at time of writing,
798/// I'm starting very conservative so that we can evolve errors in semver
799/// compatible ways.
800pub(crate) trait IntoError {
801    fn into_error(self) -> Error;
802}
803
804impl IntoError for Error {
805    #[inline(always)]
806    fn into_error(self) -> Error {
807        self
808    }
809}
810
811/// A trait for contextualizing error values.
812///
813/// This makes it easy to contextualize either `Error` or `Result<T, Error>`.
814/// Specifically, in the latter case, it absolves one of the need to call
815/// `map_err` everywhere one wants to add context to an error.
816///
817/// This trick was borrowed from `anyhow`.
818pub(crate) trait ErrorContext<T, E> {
819    /// Contextualize the given consequent error with this (`self`) error as
820    /// the cause.
821    ///
822    /// This is equivalent to saying that "consequent is caused by self."
823    ///
824    /// Note that if an `Error` is given for `kind`, then this panics if it has
825    /// a cause. (Because the cause would otherwise be dropped. An error causal
826    /// chain is just a linked list, not a tree.)
827    fn context(self, consequent: impl IntoError) -> Result<T, Error>;
828
829    /// Like `context`, but hides error construction within a closure.
830    ///
831    /// This is useful if the creation of the consequent error is not otherwise
832    /// guarded and when error construction is potentially "costly" (i.e., it
833    /// allocates). The closure avoids paying the cost of contextual error
834    /// creation in the happy path.
835    ///
836    /// Usually this only makes sense to use on a `Result<T, Error>`, otherwise
837    /// the closure is just executed immediately anyway.
838    fn with_context<C: IntoError>(
839        self,
840        consequent: impl FnOnce() -> C,
841    ) -> Result<T, Error>;
842}
843
844impl<T, E> ErrorContext<T, E> for Result<T, E>
845where
846    E: IntoError,
847{
848    #[cfg_attr(feature = "perf-inline", inline(always))]
849    fn context(self, consequent: impl IntoError) -> Result<T, Error> {
850        self.map_err(|err| {
851            err.into_error().context_impl(consequent.into_error())
852        })
853    }
854
855    #[cfg_attr(feature = "perf-inline", inline(always))]
856    fn with_context<C: IntoError>(
857        self,
858        consequent: impl FnOnce() -> C,
859    ) -> Result<T, Error> {
860        self.map_err(|err| {
861            err.into_error().context_impl(consequent().into_error())
862        })
863    }
864}
865
866#[cfg(test)]
867mod tests {
868    use super::*;
869
870    // We test that our 'Error' type is the size we expect. This isn't an API
871    // guarantee, but if the size increases, we really want to make sure we
872    // decide to do that intentionally. So this should be a speed bump. And in
873    // general, we should not increase the size without a very good reason.
874    #[test]
875    fn error_size() {
876        let mut expected_size = core::mem::size_of::<usize>();
877        if !cfg!(feature = "alloc") {
878            // oooowwwwwwwwwwwch.
879            //
880            // Like, this is horrible, right? core-only environments are
881            // precisely the place where one want to keep things slim. But
882            // in core-only, I don't know of a way to introduce any sort of
883            // indirection in the library level without using a completely
884            // different API.
885            //
886            // This is what makes me doubt that core-only Jiff is actually
887            // useful. In what context are people using a huge library like
888            // Jiff but can't define a small little heap allocator?
889            //
890            // OK, this used to be `expected_size *= 10`, but I slimmed it down
891            // to x3. Still kinda sucks right? If we tried harder, I think we
892            // could probably slim this down more. And if we were willing to
893            // sacrifice error message quality even more (like, all the way),
894            // then we could make `Error` a zero sized type. Which might
895            // actually be the right trade-off for core-only, but I'll hold off
896            // until we have some real world use cases.
897            //
898            // OK... after switching to structured errors, this jumped
899            // back up to `expected_size *= 6`. And that was with me being
900            // conscientious about what data we store inside of error types.
901            // Blech.
902            expected_size *= 6;
903        }
904        assert_eq!(expected_size, core::mem::size_of::<Error>());
905    }
906}