1mod tests;
25
26use std::panic::Location;
27
28pub use arminius_lib_macros::__MatrixApiError as MatrixApiError;
31use axum::http::StatusCode;
32use ruma::api::error::{
33 Error as MatrixError, ErrorBody as MatrixErrorBody, ErrorKind, StandardErrorBody,
34};
35use serde::{Deserialize, Serialize};
36use serde_json::json;
37
38pub trait MatrixApiError {
40 fn create_matrix_error(&self) -> MatrixError;
42}
43
44#[must_use]
46pub fn create_matrix_error(status_code: StatusCode, kind: &ErrorKind, error: &str) -> MatrixError {
47 MatrixErrorBody::Standard(StandardErrorBody::new(kind.clone(), error.to_owned()))
48 .into_error(status_code)
49}
50
51#[must_use]
53pub fn create_custom_matrix_error(
54 status_code: StatusCode,
55 errcode: &str,
56 error: &str,
57) -> MatrixError {
58 MatrixErrorBody::Json(json!({
59 "errcode": errcode,
60 "error": error,
61 }))
62 .into_error(status_code)
63}
64
65#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
67#[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::NotJson, component_crate_root = crate)]
68#[error("{0}")]
69pub struct NotJson(pub String);
70
71#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
73#[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::BadJson, component_crate_root = crate)]
74#[error("{0}")]
75pub struct BadJson(pub String);
76
77#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
126#[matrix_api_error(component_crate_root = crate)]
127pub enum InputJsonError {
128 #[error(transparent)]
131 InternalServerError(InternalServerError),
132 #[error(transparent)]
134 NotJson(NotJson),
135 #[error(transparent)]
138 BadJson(BadJson),
139}
140
141impl From<serde_json::Error> for InputJsonError {
142 fn from(value: serde_json::Error) -> Self {
143 match value.classify() {
144 serde_json::error::Category::Io => {
145 InputJsonError::InternalServerError(InternalServerError)
146 }
147 serde_json::error::Category::Data => {
148 InputJsonError::BadJson(BadJson(value.to_string()))
149 }
150 serde_json::error::Category::Eof | serde_json::error::Category::Syntax => {
151 InputJsonError::NotJson(NotJson(value.to_string()))
152 }
153 }
154 }
155}
156
157#[expect(
158 missing_docs,
159 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
160)]
161#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
162#[matrix_api_error(status_code = StatusCode::NOT_FOUND, error_kind = ErrorKind::NotFound, component_crate_root = crate)]
163#[error("Event not found")]
164pub struct EventNotFound;
165
166#[expect(
167 missing_docs,
168 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
169)]
170#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
171#[matrix_api_error(status_code = StatusCode::NOT_FOUND, error_kind = ErrorKind::NotFound, component_crate_root = crate)]
172#[error("Room not found")]
173pub struct RoomNotFound;
174
175#[expect(
176 missing_docs,
177 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
178)]
179#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
180#[matrix_api_error(status_code = StatusCode::UNAUTHORIZED, error_kind = ErrorKind::Unauthorized, component_crate_root = crate)]
181#[error("Unauthorized")]
182pub struct Unauthorized;
183
184#[expect(
185 missing_docs,
186 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
187)]
188#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize)]
189#[matrix_api_error(status_code = StatusCode::FORBIDDEN, error_kind = ErrorKind::Forbidden, component_crate_root = crate)]
190#[error("Forbidden")]
191pub struct Forbidden;
192
193#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize, Eq, PartialEq)]
198#[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::InvalidParam, component_crate_root = crate)]
199#[error("Feature not implemented: {0}")]
200pub struct FeatureNotImplemented(pub String);
201
202impl FeatureNotImplemented {
203 pub fn feature_is_used(is_used: bool, feature: String) -> Result<(), Self> {
216 if is_used { Err(Self(feature)) } else { Ok(()) }
217 }
218}
219
220#[derive(thiserror::Error, Debug, MatrixApiError, Serialize, Deserialize, PartialEq, Eq)]
227#[matrix_api_error(status_code = StatusCode::INTERNAL_SERVER_ERROR, error_kind = ErrorKind::Unknown, component_crate_root = crate)]
228#[error("Internal server error")]
229#[non_exhaustive]
230pub struct InternalServerError;
231
232impl InternalServerError {
233 #[must_use]
238 pub fn untraced_internal_server_error() -> Self {
239 InternalServerError
240 }
241}
242
243impl From<arminius_lib_db::DatabaseError> for InternalServerError {
244 #[track_caller]
245 fn from(err: arminius_lib_db::DatabaseError) -> Self {
246 let location = Location::caller();
247 tracing::error!(?err, ?location, "Error communicating with database");
248 InternalServerError
249 }
250}
251
252impl From<arminius_lib_nats::NatsError> for InternalServerError {
253 #[track_caller]
254 fn from(err: arminius_lib_nats::NatsError) -> Self {
255 let location = Location::caller();
256 tracing::error!(?err, ?location, "Error performing NATS call");
257 InternalServerError
258 }
259}
260
261pub trait ResultExt<T> {
263 fn unrecoverable_error(self, context: &str) -> Result<T, InternalServerError>;
266}
267
268impl<'a, T, E: Into<Box<dyn std::error::Error + 'a>>> ResultExt<T> for Result<T, E> {
271 #[track_caller]
272 fn unrecoverable_error(self, context: &str) -> Result<T, InternalServerError> {
273 match self {
274 Ok(value) => Ok(value),
275 Err(err) => {
276 let location = Location::caller();
277 let err = err.into();
278 tracing::error!(?err, context, ?location, "Encountered unrecoverable error");
279 Err(InternalServerError)
280 }
281 }
282 }
283}
284
285impl<T> ResultExt<T> for Option<T> {
286 #[track_caller]
287 fn unrecoverable_error(self, context: &str) -> Result<T, InternalServerError> {
288 match self {
289 Some(value) => Ok(value),
290 None => {
291 let location = Location::caller();
292 tracing::error!(context, ?location, "Expected some");
293 Err(InternalServerError)
294 }
295 }
296 }
297}
298
299impl InternalServerError {
300 #[track_caller]
303 pub fn assert(cond: bool, context: &str) -> Result<(), InternalServerError> {
304 if cond {
305 Ok(())
306 } else {
307 let location = Location::caller();
308 tracing::error!(context, ?location, "Assertion failed");
309 Err(InternalServerError)
310 }
311 }
312}