tuwunel_core/error/mod.rs
1//! Defines shared error and result types.
2//!
3//! Errors retain protocol and transport context across crate boundaries. The
4//! module also provides the macros used to construct and report them.
5
6mod err;
7mod log;
8mod panic;
9mod response;
10mod serde;
11#[cfg(test)]
12mod tests;
13
14use std::{
15 any::Any,
16 borrow::Cow,
17 convert::Infallible,
18 io::ErrorKind as IoErrorKind,
19 sync::{Mutex, PoisonError},
20};
21
22// Aliased so the variants already spelling this path qualified stay clean
23// under unused_qualifications.
24use ruma::api::error::ErrorKind as MatrixErrorKind;
25
26pub use self::{err::visit, log::*};
27use crate::utils::{assert_ref_unwind_safe, assert_send, assert_sync, assert_unwind_safe};
28
29/// Unifies failures raised by the core crate.
30///
31/// Variants preserve typed causes where available and carry contextual text for
32/// domain-specific failures. Conversion implementations allow callers to
33/// propagate common dependency errors with `?`.
34#[derive(thiserror::Error)]
35pub enum Error {
36 /// Carries an arbitrary panic payload.
37 ///
38 /// The payload is protected by a mutex so the error remains shareable
39 /// across unwind boundaries. Use the panic helpers to resume unwinding or
40 /// inspect it.
41 #[error("PANIC!")]
42 PanicAny(Mutex<Box<dyn Any + Send>>),
43
44 /// Carries a panic payload and its extracted static message.
45 ///
46 /// The message supports diagnostics without consuming the payload. The
47 /// mutex keeps the payload available across unwind boundaries.
48 #[error("PANIC! {0}")]
49 Panic(&'static str, Mutex<Box<dyn Any + Send + 'static>>),
50
51 // std
52 /// Reports a formatting failure.
53 ///
54 /// Automatic conversion preserves the original source error. Its display
55 /// text is forwarded unchanged.
56 #[error(transparent)]
57 Fmt(#[from] std::fmt::Error),
58
59 /// Reports an invalid UTF-8 byte sequence while constructing a string.
60 ///
61 /// Automatic conversion preserves the original bytes and source error. Its
62 /// display text is forwarded unchanged.
63 #[error(transparent)]
64 FromUtf8(#[from] std::string::FromUtf8Error),
65
66 /// Reports an input or output failure.
67 ///
68 /// Automatic conversion preserves the original I/O error and its kind. HTTP
69 /// response mapping may use the contained error kind.
70 #[error("I/O error: {0}")]
71 Io(#[from] std::io::Error),
72
73 /// Reports a floating-point parsing failure.
74 ///
75 /// Automatic conversion preserves the original source error. Its display
76 /// text is forwarded unchanged.
77 #[error(transparent)]
78 ParseFloat(#[from] std::num::ParseFloatError),
79
80 /// Reports an integer parsing failure.
81 ///
82 /// Automatic conversion preserves the original source error. Its display
83 /// text is forwarded unchanged.
84 #[error(transparent)]
85 ParseInt(#[from] std::num::ParseIntError),
86
87 /// Carries a dynamically typed standard error.
88 ///
89 /// The boxed source must be safe to send and share between threads. Its
90 /// display text and source chain remain available for diagnostics.
91 #[error(transparent)]
92 Std(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
93
94 /// Reports a system clock value earlier than the requested reference time.
95 ///
96 /// Automatic conversion preserves the original source error. Its display
97 /// text is forwarded unchanged.
98 #[error(transparent)]
99 SystemTime(#[from] std::time::SystemTimeError),
100
101 /// Reports failure to access thread-local state.
102 ///
103 /// Automatic conversion preserves the original source error. Its display
104 /// text is forwarded unchanged.
105 #[error(transparent)]
106 ThreadAccessError(#[from] std::thread::AccessError),
107
108 /// Reports an integer conversion outside the destination range.
109 ///
110 /// Automatic conversion preserves the original source error. Its display
111 /// text is forwarded unchanged.
112 #[error(transparent)]
113 TryFromInt(#[from] std::num::TryFromIntError),
114
115 /// Reports conversion from a slice with an incompatible length.
116 ///
117 /// Automatic conversion preserves the original source error. Its display
118 /// text is forwarded unchanged.
119 #[error(transparent)]
120 TryFromSlice(#[from] std::array::TryFromSliceError),
121
122 /// Reports an invalid borrowed UTF-8 byte sequence.
123 ///
124 /// Automatic conversion preserves the original source error. Its display
125 /// text is forwarded unchanged.
126 #[error(transparent)]
127 Utf8(#[from] std::str::Utf8Error),
128
129 // third-party
130 /// Reports that a fixed-capacity collection cannot accept another item.
131 ///
132 /// Automatic conversion preserves the original capacity error. Its display
133 /// text is forwarded unchanged.
134 #[error(transparent)]
135 CapacityError(#[from] arrayvec::CapacityError),
136
137 /// Reports failure to parse Cargo manifest data.
138 ///
139 /// Automatic conversion preserves the original source error. Its display
140 /// text is forwarded unchanged.
141 #[error(transparent)]
142 CargoToml(#[from] cargo_toml::Error),
143
144 /// Reports a command-line parsing or presentation failure.
145 ///
146 /// Automatic conversion preserves the original Clap error. Its display text
147 /// is forwarded unchanged.
148 #[error(transparent)]
149 Clap(#[from] clap::error::Error),
150
151 /// Reports a Unix system error number.
152 ///
153 /// Automatic conversion preserves the original error number. Its display
154 /// text is forwarded unchanged.
155 #[cfg(unix)]
156 #[error(transparent)]
157 Errno(#[from] nix::errno::Errno),
158
159 /// Reports rejection of a required Axum request extension.
160 ///
161 /// Automatic conversion preserves the extractor rejection. Its display text
162 /// is forwarded unchanged.
163 #[error(transparent)]
164 Extension(#[from] axum::extract::rejection::ExtensionRejection),
165
166 /// Reports a configuration extraction failure.
167 ///
168 /// The boxed Figment error preserves its complete source and diagnostic
169 /// context. A dedicated conversion boxes it for ordinary `?` propagation.
170 #[error(transparent)]
171 Figment(Box<figment::error::Error>),
172
173 /// Reports failure to deserialize an HTML form.
174 ///
175 /// Automatic conversion preserves the original source error. Its display
176 /// text is forwarded unchanged.
177 #[error(transparent)]
178 HtmlFormDe(#[from] serde_html_form::de::Error),
179
180 /// Reports failure to serialize an HTML form.
181 ///
182 /// Automatic conversion preserves the original source error. Its display
183 /// text is forwarded unchanged.
184 #[error(transparent)]
185 HtmlFormSer(#[from] serde_html_form::ser::Error),
186
187 /// Reports failure to construct an HTTP value.
188 ///
189 /// Automatic conversion preserves the original source error. Its display
190 /// text is forwarded unchanged.
191 #[error(transparent)]
192 Http(#[from] http::Error),
193
194 /// Reports an invalid HTTP header value.
195 ///
196 /// Automatic conversion preserves the original source error. Its display
197 /// text is forwarded unchanged.
198 #[error(transparent)]
199 HttpHeader(#[from] http::header::InvalidHeaderValue),
200
201 /// Reports failure of a spawned asynchronous task.
202 ///
203 /// The Tokio join error records cancellation and panic state. Panic helpers
204 /// can recover its payload when the task panicked.
205 #[error("Join error: {0}")]
206 JoinError(#[from] tokio::task::JoinError),
207
208 /// Reports failure to serialize or deserialize JSON.
209 ///
210 /// Automatic conversion preserves the original source error. Matrix error
211 /// mapping classifies this variant as invalid JSON.
212 #[error(transparent)]
213 Json(#[from] serde_json::Error),
214
215 /// Reports failure to parse a Matrix-compatible JavaScript integer.
216 ///
217 /// Automatic conversion preserves the re-exported integer error. Matrix
218 /// response mapping treats it as a bad request.
219 #[error(transparent)]
220 JsParseInt(#[from] ruma::JsParseIntError), // js_int re-export
221
222 /// Reports a value outside the Matrix JavaScript-integer range.
223 ///
224 /// Automatic conversion preserves the re-exported conversion error. Matrix
225 /// response mapping treats it as a bad request.
226 #[error(transparent)]
227 JsTryFromInt(#[from] ruma::JsTryFromIntError), // js_int re-export
228
229 /// Reports an object-storage operation failure.
230 ///
231 /// Automatic conversion preserves the backend source error. Its display
232 /// text is forwarded unchanged.
233 #[error(transparent)]
234 ObjectStore(#[from] object_store::Error),
235
236 /// Reports rejection of an Axum path parameter.
237 ///
238 /// Automatic conversion preserves the extractor rejection. Its display text
239 /// is forwarded unchanged.
240 #[error(transparent)]
241 Path(#[from] axum::extract::rejection::PathRejection),
242
243 /// Reports access to a poisoned synchronization primitive.
244 ///
245 /// The stored text reports the poisoning without retaining the guard.
246 /// Poison conversion supplies the originating error text.
247 #[error("{0}")]
248 Poison(Cow<'static, str>),
249
250 /// Reports an invalid regular expression.
251 ///
252 /// Automatic conversion preserves the original source error. The formatted
253 /// message identifies the regex failure.
254 #[error("Regex error: {0}")]
255 Regex(#[from] regex::Error),
256
257 /// Reports an HTTP client request failure.
258 ///
259 /// Automatic conversion preserves status and transport details from
260 /// Reqwest. HTTP response mapping reuses its status when one is available.
261 #[error("Request error: {0}")]
262 Reqwest(#[from] reqwest::Error),
263
264 /// Reports a custom deserialization failure.
265 ///
266 /// The message may borrow static text or own formatted context. It is
267 /// exposed directly as the error display.
268 #[error("{0}")]
269 SerdeDe(Cow<'static, str>),
270
271 /// Reports a custom serialization failure.
272 ///
273 /// The message may borrow static text or own formatted context. It is
274 /// exposed directly as the error display.
275 #[error("{0}")]
276 SerdeSer(Cow<'static, str>),
277
278 /// Reports failure to deserialize TOML.
279 ///
280 /// Automatic conversion preserves the original source error. Its display
281 /// text is forwarded unchanged.
282 #[error(transparent)]
283 TomlDe(#[from] toml::de::Error),
284
285 /// Reports failure to serialize TOML.
286 ///
287 /// Automatic conversion preserves the original source error. Its display
288 /// text is forwarded unchanged.
289 #[error(transparent)]
290 TomlSer(#[from] toml::ser::Error),
291
292 /// Reports an invalid tracing filter directive.
293 ///
294 /// Automatic conversion preserves the filter parser's source error. The
295 /// formatted message identifies the tracing subsystem.
296 #[error("Tracing filter error: {0}")]
297 TracingFilter(#[from] tracing_subscriber::filter::ParseError),
298
299 /// Reports failure to reload a tracing layer.
300 ///
301 /// Automatic conversion preserves the reload source error. The formatted
302 /// message identifies the tracing subsystem.
303 #[error("Tracing reload error: {0}")]
304 TracingReload(#[from] tracing_subscriber::reload::Error),
305
306 /// Reports rejection of a typed HTTP header.
307 ///
308 /// Automatic conversion preserves the extractor rejection. Its display text
309 /// is forwarded unchanged.
310 #[error(transparent)]
311 TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection),
312
313 /// Reports failure to parse a URL.
314 ///
315 /// Automatic conversion preserves the original source error. Its display
316 /// text is forwarded unchanged.
317 #[error(transparent)]
318 UrlParse(#[from] url::ParseError),
319
320 /// Reports failure to serialize or deserialize YAML.
321 ///
322 /// Automatic conversion preserves the original source error. Its display
323 /// text is forwarded unchanged.
324 #[error(transparent)]
325 Yaml(#[from] serde_yaml::Error),
326
327 // ruma/tuwunel
328 /// Reports an arithmetic operation that cannot produce a valid result.
329 ///
330 /// The message records contextual conversion, range, overflow, and
331 /// underflow failures. It can supplement lower-level typed numeric source
332 /// errors.
333 #[error("Arithmetic operation failed: {0}")]
334 Arithmetic(Cow<'static, str>),
335
336 /// State-res `auth_check` rejection sentinel.
337 ///
338 /// Surfaces to the wire as 403 / M_FORBIDDEN with the Display text
339 /// `Auth check failed: {inner}`. Exists so callers can pattern-match the
340 /// cause without grepping the message text.
341 #[error("Auth check failed: {0}")]
342 AuthCheck(Box<Self>),
343
344 /// Reports a legacy structured Matrix request error.
345 ///
346 /// The variant pairs a Matrix error kind with static public text. Response
347 /// mapping derives the appropriate HTTP status from that kind.
348 #[error("{0}: {1}")]
349 BadRequest(ruma::api::error::ErrorKind, &'static str), //TODO: remove
350
351 /// Reports an invalid or unusable response from a remote server.
352 ///
353 /// The message carries protocol context suitable for diagnostics. No typed
354 /// remote response is retained.
355 #[error("{0}")]
356 BadServerResponse(Cow<'static, str>),
357
358 /// Reports invalid canonical JSON.
359 ///
360 /// Automatic conversion preserves the original canonicalization error.
361 /// Matrix error mapping classifies this variant as invalid JSON.
362 #[error(transparent)]
363 CanonicalJson(#[from] ruma::CanonicalJsonError),
364
365 /// Reports an invalid configuration directive.
366 ///
367 /// The static directive name identifies the setting and the accompanying
368 /// message explains why its value cannot be used.
369 #[error("There was a problem with the '{0}' directive in your configuration: {1}")]
370 Config(&'static str, Cow<'static, str>),
371
372 /// Reports a resource conflict.
373 ///
374 /// This variant currently represents an already occupied room alias. HTTP
375 /// response mapping emits a conflict status.
376 #[error("{0}")]
377 Conflict(Cow<'static, str>), // This is only needed for when a room alias already exists
378
379 /// Reports an invalid Matrix content-disposition header.
380 ///
381 /// Automatic conversion preserves the original parser error. Its display
382 /// text is forwarded unchanged.
383 #[error(transparent)]
384 ContentDisposition(#[from] ruma::http_headers::ContentDispositionParseError),
385
386 /// Reports a database operation or invariant failure.
387 ///
388 /// The message carries storage context without exposing it in sanitized
389 /// client-facing output. Database failures default to an internal status.
390 #[error("{0}")]
391 Database(Cow<'static, str>),
392
393 /// Reports use of a feature disabled by server configuration.
394 ///
395 /// The stored feature name is included in the public message. Matrix
396 /// response mapping assigns the matching feature-disabled error kind.
397 #[error("Feature '{0}' is not available on this server.")]
398 FeatureDisabled(Cow<'static, str>),
399
400 /// Reports an error response received from a federated server.
401 ///
402 /// The variant preserves both the origin and its structured Matrix error,
403 /// so internal callers can dispatch on what the remote said. Response
404 /// mapping does not forward that status and kind unconditionally.
405 #[error("Remote server {0} responded with: {1}")]
406 Federation(ruma::OwnedServerName, ruma::api::error::Error),
407
408 /// Carries a preconstructed HTTP status and JSON response body.
409 ///
410 /// Response mapping preserves the caller-selected status. The formatted
411 /// JSON becomes the message of a standard Matrix error response.
412 #[error("{0}: {1:#?}")]
413 HttpJson(http::StatusCode, axum::Json<serde_json::Value>),
414
415 /// Reports an invariant violation in a room's persisted state.
416 ///
417 /// The static message names the failed invariant and the room identifier
418 /// locates the affected state.
419 #[error("{0} in {1}")]
420 InconsistentRoomState(&'static str, ruma::OwnedRoomId),
421
422 /// Reports failure to convert a Matrix response into HTTP form.
423 ///
424 /// Automatic conversion preserves the original Ruma source error. Its
425 /// display text is forwarded unchanged.
426 #[error(transparent)]
427 IntoHttp(#[from] ruma::api::error::IntoHttpError),
428
429 /// Reports an LDAP operation failure.
430 ///
431 /// The message records directory-service context not represented by a
432 /// common typed source. Response mapping treats it as an internal error.
433 #[error("{0}")]
434 Ldap(Cow<'static, str>),
435
436 /// Reports an invalid Matrix content URI.
437 ///
438 /// Automatic conversion preserves the original URI error. Its display text
439 /// is forwarded unchanged.
440 #[error(transparent)]
441 Mxc(#[from] ruma::MxcUriError),
442
443 /// Reports an invalid Matrix identifier.
444 ///
445 /// Automatic conversion preserves the original identifier parser error. Its
446 /// display text is forwarded unchanged.
447 #[error(transparent)]
448 Mxid(#[from] ruma::IdParseError),
449
450 /// Reports invalid room power-level content or arithmetic.
451 ///
452 /// Automatic conversion preserves the original Ruma power-level error. Its
453 /// display text is forwarded unchanged.
454 #[error(transparent)]
455 PowerLevels(#[from] ruma::events::room::power_levels::PowerLevelsError),
456
457 /// Reports failure to redact canonical JSON from a remote server.
458 ///
459 /// The variant records the origin alongside the invalid canonical field.
460 /// The formatted message keeps both pieces of context.
461 #[error("from {0}: {1}")]
462 Redaction(ruma::OwnedServerName, ruma::canonical_json::CanonicalJsonFieldError),
463
464 /// Carries a structured Matrix client error response.
465 ///
466 /// The variant stores the Matrix error kind, public message, and preferred
467 /// HTTP status. Response mapping may refine the status from the kind.
468 #[error("{0}: {1}")]
469 Request(ruma::api::error::ErrorKind, Cow<'static, str>, http::StatusCode),
470
471 /// Carries a client error whose HTTP status is authoritative.
472 ///
473 /// `Request` treats a bad-request status as unset and lets the kind promote
474 /// it. Use this variant where a specification mandates a status the
475 /// promotion table would override.
476 #[error("{0}: {1}")]
477 RequestStatus(MatrixErrorKind, Cow<'static, str>, http::StatusCode),
478
479 /// Reports a structured Matrix API error.
480 ///
481 /// Automatic conversion preserves the Ruma status, kind, and message.
482 /// Response mapping forwards those structured fields.
483 #[error(transparent)]
484 Ruma(#[from] ruma::api::error::Error),
485
486 /// Reports a Matrix signature verification failure.
487 ///
488 /// Automatic conversion preserves the original verification error. Its
489 /// display text is forwarded unchanged.
490 #[error(transparent)]
491 Signatures(#[from] ruma::signatures::VerificationError),
492
493 /// Reports invalid JSON encountered during signature processing.
494 ///
495 /// Automatic conversion preserves the signature library's JSON error. Its
496 /// display text is forwarded unchanged.
497 #[error(transparent)]
498 SignaturesJson(#[from] ruma::signatures::JsonError),
499
500 /// Requests an interactive-authentication challenge response.
501 ///
502 /// The contained UIAA information is serialized for the client rather than
503 /// treated as an opaque internal failure.
504 #[error("uiaa")]
505 Uiaa(ruma::api::client::uiaa::UiaaInfo),
506
507 // unique / untyped
508 /// Reports an untyped core failure.
509 ///
510 /// The message may borrow static text or own formatted context. This
511 /// fallback is used when no structured variant represents the failure.
512 #[error("{0}")]
513 Err(Cow<'static, str>),
514}
515
516static _IS_SEND: () = assert_send::<Error>();
517static _IS_SYNC: () = assert_sync::<Error>();
518static _IS_UNWIND_SAFE: () = assert_unwind_safe::<Error>();
519static _IS_REF_UNWIND_SAFE: () = assert_ref_unwind_safe::<Error>();
520
521impl Error {
522 /// Captures the operating system's most recent error for the current
523 /// thread.
524 ///
525 /// The error is sampled when this function is called and wrapped as
526 /// [`Error::Io`]. Platform-specific code remains available through the
527 /// source.
528 #[inline]
529 #[must_use]
530 pub fn from_errno() -> Self { Self::Io(std::io::Error::last_os_error()) }
531
532 /// Constructs a database error from static diagnostic text.
533 ///
534 /// The error helper records the call site while preserving the supplied
535 /// message. Callers exposing it publicly can use
536 /// [`Error::sanitized_message`].
537 //#[deprecated]
538 pub fn bad_database(message: &'static str) -> Self {
539 crate::err!(Database(error!("{message}")))
540 }
541
542 /// Produces an error message safe for public responses.
543 ///
544 /// Database and I/O details are replaced with generic text to avoid leaking
545 /// sensitive context. Other variants retain their normal message.
546 pub fn sanitized_message(&self) -> String {
547 match self {
548 | Self::Database(..) => String::from("Database error occurred."),
549 | Self::Io(..) => String::from("I/O error occurred."),
550 | _ => self.message(),
551 }
552 }
553
554 /// Formats the diagnostic message for this error.
555 ///
556 /// Federation errors include their origin and Ruma errors use their Matrix
557 /// response message. Other variants use their
558 /// [`Display`](std::fmt::Display) implementation.
559 pub fn message(&self) -> String {
560 match self {
561 | Self::Federation(origin, error) => format!("Answer from {origin}: {error}"),
562 | Self::Ruma(error) => response::ruma_error_message(error),
563 | _ => format!("{self}"),
564 }
565 }
566
567 /// Returns the Matrix error kind represented by this error.
568 ///
569 /// Structured request and federation variants preserve their supplied kind.
570 /// Unclassified internal errors map to `M_UNKNOWN`.
571 #[inline]
572 pub fn kind(&self) -> ruma::api::error::ErrorKind {
573 use ruma::api::error::{
574 ErrorKind,
575 ErrorKind::{FeatureDisabled, NotJson, Unknown},
576 };
577
578 match self {
579 | Self::FeatureDisabled(..) => FeatureDisabled,
580 | Self::CanonicalJson(..) | Self::Json(..) => NotJson,
581 | Self::AuthCheck(..) => ErrorKind::forbidden(),
582 | Self::BadRequest(kind, ..)
583 | Self::Request(kind, ..)
584 | Self::RequestStatus(kind, ..) => kind.clone(),
585 | Self::Federation(_, error) | Self::Ruma(error) =>
586 response::ruma_error_kind(error).clone(),
587 | _ => Unknown,
588 }
589 }
590
591 /// Returns the HTTP status represented by this error.
592 ///
593 /// Structured variants preserve or derive their protocol status, while I/O
594 /// and client errors use their available status metadata. Unclassified
595 /// failures map to an internal-server-error status.
596 pub fn status_code(&self) -> http::StatusCode {
597 use http::StatusCode;
598
599 match self {
600 | Self::AuthCheck(..) => StatusCode::FORBIDDEN,
601 | Self::Conflict(_) => StatusCode::CONFLICT, // room alias exists
602 | Self::Federation(_, error) | Self::Ruma(error) => error.status_code,
603 | Self::FeatureDisabled(..)
604 | Self::CanonicalJson(..)
605 | Self::Json(..)
606 | Self::JsParseInt(..)
607 | Self::JsTryFromInt(..) => response::bad_request_code(&self.kind()),
608 | Self::BadRequest(kind, ..) => response::bad_request_code(kind),
609 | Self::Request(kind, _, code) => response::status_code(kind, *code),
610 | Self::Io(error) => response::io_error_code(error.kind()),
611 | Self::HttpJson(code, ..) | Self::RequestStatus(.., code) => *code,
612 | Self::Reqwest(error) => error
613 .status()
614 .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
615 | _ => StatusCode::INTERNAL_SERVER_ERROR,
616 }
617 }
618
619 /// Tests whether this error maps to an HTTP not-found status.
620 ///
621 /// The test includes contained error types whose status mapping yields 404.
622 /// Callers can use it to treat `Err` as the absent case in place of a
623 /// nested `Option`.
624 #[inline]
625 pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND }
626
627 /// Tests whether this error reports an interrupted operation.
628 ///
629 /// [`Server::check_running`] produces this shape once shutdown begins, so a
630 /// caller can tell work abandoned at a cancellation point from work that
631 /// genuinely failed. It matches the I/O variant only, so a layer that
632 /// rewraps the error into another variant hides the cancellation.
633 ///
634 /// [`Server::check_running`]: crate::Server::check_running
635 #[inline]
636 pub fn is_interrupted(&self) -> bool {
637 matches!(self, Self::Io(error) if error.kind() == IoErrorKind::Interrupted)
638 }
639}
640
641impl std::fmt::Debug for Error {
642 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
643 write!(f, "{}", self.message())
644 }
645}
646
647impl<T> From<PoisonError<T>> for Error {
648 #[cold]
649 #[inline(never)]
650 fn from(e: PoisonError<T>) -> Self { Self::Poison(e.to_string().into()) }
651}
652
653impl From<figment::error::Error> for Error {
654 #[cold]
655 #[inline(never)]
656 fn from(e: figment::error::Error) -> Self { Self::Figment(Box::new(e)) }
657}
658
659#[expect(clippy::fallible_impl_from)]
660impl From<Infallible> for Error {
661 #[cold]
662 #[inline(never)]
663 fn from(_e: Infallible) -> Self {
664 panic!("infallible error should never exist");
665 }
666}
667
668/// Marks an impossible [`Infallible`] error path.
669///
670/// The argument cannot be constructed in safe code, so reaching this function
671/// indicates a violated invariant.
672///
673/// # Panics
674///
675/// Always panics because an `Infallible` error cannot legitimately exist.
676#[cold]
677#[inline(never)]
678pub fn infallible(_e: &Infallible) {
679 panic!("infallible error should never exist");
680}
681
682/// Produces a public-safe message from an owned error.
683///
684/// Its by-value signature adapts [`Error::sanitized_message`] for iterator and
685/// future combinators that consume their item. Sanitization behavior is
686/// identical to the method.
687#[inline]
688#[must_use]
689#[expect(clippy::needless_pass_by_value)]
690pub fn sanitized_message(e: Error) -> String { e.sanitized_message() }