1mod err;
2mod log;
3mod panic;
4mod response;
5mod serde;
6
7use std::{
8 any::Any,
9 borrow::Cow,
10 convert::Infallible,
11 sync::{Mutex, PoisonError},
12};
13
14pub use self::{err::visit, log::*};
15use crate::utils::{assert_ref_unwind_safe, assert_send, assert_sync, assert_unwind_safe};
16
17#[derive(thiserror::Error)]
18pub enum Error {
19 #[error("PANIC!")]
20 PanicAny(Mutex<Box<dyn Any + Send>>),
21 #[error("PANIC! {0}")]
22 Panic(&'static str, Mutex<Box<dyn Any + Send + 'static>>),
23
24 #[error(transparent)]
26 Fmt(#[from] std::fmt::Error),
27 #[error(transparent)]
28 FromUtf8(#[from] std::string::FromUtf8Error),
29 #[error("I/O error: {0}")]
30 Io(#[from] std::io::Error),
31 #[error(transparent)]
32 ParseFloat(#[from] std::num::ParseFloatError),
33 #[error(transparent)]
34 ParseInt(#[from] std::num::ParseIntError),
35 #[error(transparent)]
36 Std(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
37 #[error(transparent)]
38 SystemTime(#[from] std::time::SystemTimeError),
39 #[error(transparent)]
40 ThreadAccessError(#[from] std::thread::AccessError),
41 #[error(transparent)]
42 TryFromInt(#[from] std::num::TryFromIntError),
43 #[error(transparent)]
44 TryFromSlice(#[from] std::array::TryFromSliceError),
45 #[error(transparent)]
46 Utf8(#[from] std::str::Utf8Error),
47
48 #[error(transparent)]
50 CapacityError(#[from] arrayvec::CapacityError),
51 #[error(transparent)]
52 CargoToml(#[from] cargo_toml::Error),
53 #[error(transparent)]
54 Clap(#[from] clap::error::Error),
55 #[cfg(unix)]
56 #[error(transparent)]
57 Errno(#[from] nix::errno::Errno),
58 #[error(transparent)]
59 Extension(#[from] axum::extract::rejection::ExtensionRejection),
60 #[error(transparent)]
61 Figment(Box<figment::error::Error>),
62 #[error(transparent)]
63 HtmlFormDe(#[from] serde_html_form::de::Error),
64 #[error(transparent)]
65 HtmlFormSer(#[from] serde_html_form::ser::Error),
66 #[error(transparent)]
67 Http(#[from] http::Error),
68 #[error(transparent)]
69 HttpHeader(#[from] http::header::InvalidHeaderValue),
70 #[error("Join error: {0}")]
71 JoinError(#[from] tokio::task::JoinError),
72 #[error(transparent)]
73 Json(#[from] serde_json::Error),
74 #[error(transparent)]
75 JsParseInt(#[from] ruma::JsParseIntError), #[error(transparent)]
77 JsTryFromInt(#[from] ruma::JsTryFromIntError), #[error(transparent)]
79 ObjectStore(#[from] object_store::Error),
80 #[error(transparent)]
81 Path(#[from] axum::extract::rejection::PathRejection),
82 #[error("Mutex poisoned: {0}")]
83 Poison(Cow<'static, str>),
84 #[error("Regex error: {0}")]
85 Regex(#[from] regex::Error),
86 #[error("Request error: {0}")]
87 Reqwest(#[from] reqwest::Error),
88 #[error("{0}")]
89 SerdeDe(Cow<'static, str>),
90 #[error("{0}")]
91 SerdeSer(Cow<'static, str>),
92 #[error(transparent)]
93 TomlDe(#[from] toml::de::Error),
94 #[error(transparent)]
95 TomlSer(#[from] toml::ser::Error),
96 #[error("Tracing filter error: {0}")]
97 TracingFilter(#[from] tracing_subscriber::filter::ParseError),
98 #[error("Tracing reload error: {0}")]
99 TracingReload(#[from] tracing_subscriber::reload::Error),
100 #[error(transparent)]
101 TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection),
102 #[error(transparent)]
103 UrlParse(#[from] url::ParseError),
104 #[error(transparent)]
105 Yaml(#[from] serde_yaml::Error),
106
107 #[error("Arithmetic operation failed: {0}")]
109 Arithmetic(Cow<'static, str>),
110
111 #[error("Auth check failed: {0}")]
117 AuthCheck(Box<Self>),
118 #[error("{0}: {1}")]
119 BadRequest(ruma::api::error::ErrorKind, &'static str), #[error("{0}")]
121 BadServerResponse(Cow<'static, str>),
122 #[error(transparent)]
123 CanonicalJson(#[from] ruma::CanonicalJsonError),
124 #[error("There was a problem with the '{0}' directive in your configuration: {1}")]
125 Config(&'static str, Cow<'static, str>),
126 #[error("{0}")]
127 Conflict(Cow<'static, str>), #[error(transparent)]
129 ContentDisposition(#[from] ruma::http_headers::ContentDispositionParseError),
130 #[error("{0}")]
131 Database(Cow<'static, str>),
132 #[error("Feature '{0}' is not available on this server.")]
133 FeatureDisabled(Cow<'static, str>),
134 #[error("Remote server {0} responded with: {1}")]
135 Federation(ruma::OwnedServerName, ruma::api::error::Error),
136 #[error("{0}: {1:#?}")]
137 HttpJson(http::StatusCode, axum::Json<serde_json::Value>),
138 #[error("{0} in {1}")]
139 InconsistentRoomState(&'static str, ruma::OwnedRoomId),
140 #[error(transparent)]
141 IntoHttp(#[from] ruma::api::error::IntoHttpError),
142 #[error("{0}")]
143 Ldap(Cow<'static, str>),
144 #[error(transparent)]
145 Mxc(#[from] ruma::MxcUriError),
146 #[error(transparent)]
147 Mxid(#[from] ruma::IdParseError),
148 #[error(transparent)]
149 PowerLevels(#[from] ruma::events::room::power_levels::PowerLevelsError),
150 #[error("from {0}: {1}")]
151 Redaction(ruma::OwnedServerName, ruma::canonical_json::CanonicalJsonFieldError),
152 #[error("{0}: {1}")]
153 Request(ruma::api::error::ErrorKind, Cow<'static, str>, http::StatusCode),
154 #[error(transparent)]
155 Ruma(#[from] ruma::api::error::Error),
156 #[error(transparent)]
157 Signatures(#[from] ruma::signatures::VerificationError),
158 #[error(transparent)]
159 SignaturesJson(#[from] ruma::signatures::JsonError),
160 #[error("uiaa")]
161 Uiaa(ruma::api::client::uiaa::UiaaInfo),
162
163 #[error("{0}")]
165 Err(Cow<'static, str>),
166}
167
168static _IS_SEND: () = assert_send::<Error>();
169static _IS_SYNC: () = assert_sync::<Error>();
170static _IS_UNWIND_SAFE: () = assert_unwind_safe::<Error>();
171static _IS_REF_UNWIND_SAFE: () = assert_ref_unwind_safe::<Error>();
172
173impl Error {
174 #[inline]
175 #[must_use]
176 pub fn from_errno() -> Self { Self::Io(std::io::Error::last_os_error()) }
177
178 pub fn bad_database(message: &'static str) -> Self {
180 crate::err!(Database(error!("{message}")))
181 }
182
183 pub fn sanitized_message(&self) -> String {
185 match self {
186 | Self::Database(..) => String::from("Database error occurred."),
187 | Self::Io(..) => String::from("I/O error occurred."),
188 | _ => self.message(),
189 }
190 }
191
192 pub fn message(&self) -> String {
194 match self {
195 | Self::Federation(origin, error) => format!("Answer from {origin}: {error}"),
196 | Self::Ruma(error) => response::ruma_error_message(error),
197 | _ => format!("{self}"),
198 }
199 }
200
201 #[inline]
203 pub fn kind(&self) -> ruma::api::error::ErrorKind {
204 use ruma::api::error::{
205 ErrorKind,
206 ErrorKind::{FeatureDisabled, NotJson, Unknown},
207 };
208
209 match self {
210 | Self::FeatureDisabled(..) => FeatureDisabled,
211 | Self::CanonicalJson(..) | Self::Json(..) => NotJson,
212 | Self::AuthCheck(..) => ErrorKind::forbidden(),
213 | Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
214 | Self::Federation(_, error) | Self::Ruma(error) =>
215 response::ruma_error_kind(error).clone(),
216 | _ => Unknown,
217 }
218 }
219
220 pub fn status_code(&self) -> http::StatusCode {
223 use http::StatusCode;
224
225 match self {
226 | Self::AuthCheck(..) => StatusCode::FORBIDDEN,
227 | Self::Conflict(_) => StatusCode::CONFLICT, | Self::Federation(_, error) | Self::Ruma(error) => error.status_code,
229 | Self::FeatureDisabled(..)
230 | Self::CanonicalJson(..)
231 | Self::Json(..)
232 | Self::JsParseInt(..)
233 | Self::JsTryFromInt(..) => response::bad_request_code(&self.kind()),
234 | Self::BadRequest(kind, ..) => response::bad_request_code(kind),
235 | Self::Request(kind, _, code) => response::status_code(kind, *code),
236 | Self::Io(error) => response::io_error_code(error.kind()),
237 | Self::HttpJson(code, ..) => *code,
238 | Self::Reqwest(error) => error
239 .status()
240 .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
241 | _ => StatusCode::INTERNAL_SERVER_ERROR,
242 }
243 }
244
245 #[inline]
250 pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND }
251}
252
253impl std::fmt::Debug for Error {
254 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255 write!(f, "{}", self.message())
256 }
257}
258
259impl<T> From<PoisonError<T>> for Error {
260 #[cold]
261 #[inline(never)]
262 fn from(e: PoisonError<T>) -> Self { Self::Poison(e.to_string().into()) }
263}
264
265impl From<figment::error::Error> for Error {
266 #[cold]
267 #[inline(never)]
268 fn from(e: figment::error::Error) -> Self { Self::Figment(Box::new(e)) }
269}
270
271#[expect(clippy::fallible_impl_from)]
272impl From<Infallible> for Error {
273 #[cold]
274 #[inline(never)]
275 fn from(_e: Infallible) -> Self {
276 panic!("infallible error should never exist");
277 }
278}
279
280#[cold]
281#[inline(never)]
282pub fn infallible(_e: &Infallible) {
283 panic!("infallible error should never exist");
284}
285
286#[inline]
288#[must_use]
289#[expect(clippy::needless_pass_by_value)]
290pub fn sanitized_message(e: Error) -> String { e.sanitized_message() }