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}