Skip to main content

jiff/error/
mod.rs

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