Skip to main content

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() }