From 7a8c409ff902524716aab86ab80cf4536e7da3e0 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Fri, 6 Feb 2026 01:01:39 +0000 Subject: [PATCH] refactor: Use snafu This should gradtly improve the debugging experience by allowing tracking backtraces to get more context from the error. A lot of the touced files here are just cleaning up the old way of creating errors. --- Cargo.lock | 26 +- Cargo.toml | 9 +- src/api/client/account.rs | 14 +- src/api/client/device.rs | 8 +- src/api/client/keys.rs | 12 +- src/api/client/media.rs | 4 +- src/api/client/message.rs | 6 +- src/api/client/push.rs | 32 +- src/api/client/room/upgrade.rs | 12 +- src/api/client/session.rs | 8 +- src/api/client/to_device.rs | 4 +- src/api/client/well_known.rs | 17 +- src/api/router/auth.rs | 20 +- src/api/server/event_auth.rs | 9 +- src/api/server/invite.rs | 4 +- src/api/server/make_join.rs | 4 +- src/api/server/make_knock.rs | 6 +- src/api/server/publicrooms.rs | 16 +- src/api/server/query.rs | 11 +- src/api/server/send.rs | 2 +- src/api/server/user.rs | 13 +- src/api/server/well_known.rs | 6 +- src/core/Cargo.toml | 3 +- src/core/error/err.rs | 159 ++++- src/core/error/mod.rs | 587 +++++++++++++----- src/core/error/panic.rs | 13 +- src/core/error/response.rs | 4 +- src/core/error/serde.rs | 10 +- src/core/matrix/pdu/redact.rs | 13 +- src/core/matrix/state_res/benches.rs | 12 +- src/core/matrix/state_res/error.rs | 37 +- src/core/matrix/state_res/event_auth.rs | 7 +- src/core/matrix/state_res/mod.rs | 70 ++- src/core/matrix/state_res/room_version.rs | 8 +- src/core/matrix/state_res/test_utils.rs | 4 +- src/core/mod.rs | 2 + src/core/utils/sys/compute.rs | 6 +- src/database/de.rs | 19 +- src/database/ser.rs | 5 +- src/router/router.rs | 9 +- src/service/appservice/mod.rs | 8 +- src/service/federation/execute.rs | 7 +- src/service/media/remote.rs | 4 +- .../rooms/event_handler/resolve_state.rs | 13 +- src/service/rooms/spaces/pagination_token.rs | 4 +- src/service/rooms/timeline/create.rs | 9 +- src/service/uiaa/mod.rs | 8 +- src/service/users/mod.rs | 12 +- src/web/Cargo.toml | 2 +- src/web/mod.rs | 16 +- src/web/templates/error.html.j2 | 4 +- 51 files changed, 907 insertions(+), 391 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e248b9d..b6e2432c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1014,6 +1014,7 @@ dependencies = [ "nix", "num-traits", "parking_lot", + "paste", "rand 0.10.0", "rand_core 0.6.4", "regex", @@ -1027,7 +1028,7 @@ dependencies = [ "serde_regex", "smallstr", "smallvec", - "thiserror 2.0.18", + "snafu", "tikv-jemalloc-ctl", "tikv-jemalloc-sys", "tikv-jemallocator", @@ -1154,7 +1155,7 @@ dependencies = [ "conduwuit_service", "futures", "rand 0.10.0", - "thiserror 2.0.18", + "snafu", "tracing", ] @@ -4911,6 +4912,27 @@ dependencies = [ "serde", ] +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.5.10" diff --git a/Cargo.toml b/Cargo.toml index 5ce0c028..67581d15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -307,9 +307,14 @@ features = [ ] # Used for conduwuit::Error type -[workspace.dependencies.thiserror] -version = "2.0.12" +[workspace.dependencies.snafu] +version = "0.8" default-features = false +features = ["std", "rust_1_81"] + +# Used for macro name generation +[workspace.dependencies.paste] +version = "1.0" # Used when hashing the state [workspace.dependencies.ring] diff --git a/src/api/client/account.rs b/src/api/client/account.rs index e4d008b3..7dfb2afa 100644 --- a/src/api/client/account.rs +++ b/src/api/client/account.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduwuit::{ - Err, Error, Event, Result, debug_info, err, error, info, + Err, Event, Result, debug_info, err, error, info, matrix::pdu::PduBuilder, utils::{self, ReadyExt, stream::BroadbandExt}, warn, @@ -387,7 +387,7 @@ pub(crate) async fn register_route( ) .await?; if !worked { - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); } // Success! }, @@ -401,7 +401,7 @@ pub(crate) async fn register_route( &uiaainfo, json, ); - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); }, | _ => { return Err!(Request(NotJson("JSON body is not valid"))); @@ -661,7 +661,7 @@ pub(crate) async fn change_password_route( .await?; if !worked { - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); } // Success! @@ -673,7 +673,7 @@ pub(crate) async fn change_password_route( .uiaa .create(sender_user, body.sender_device(), &uiaainfo, json); - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); }, | _ => { return Err!(Request(NotJson("JSON body is not valid"))); @@ -791,7 +791,7 @@ pub(crate) async fn deactivate_route( .await?; if !worked { - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); } // Success! }, @@ -802,7 +802,7 @@ pub(crate) async fn deactivate_route( .uiaa .create(sender_user, body.sender_device(), &uiaainfo, json); - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); }, | _ => { return Err!(Request(NotJson("JSON body is not valid"))); diff --git a/src/api/client/device.rs b/src/api/client/device.rs index c88012cd..9ae89f4c 100644 --- a/src/api/client/device.rs +++ b/src/api/client/device.rs @@ -1,6 +1,6 @@ use axum::extract::State; use axum_client_ip::InsecureClientIp; -use conduwuit::{Err, Error, Result, debug, err, utils}; +use conduwuit::{Err, Result, debug, err, utils}; use futures::StreamExt; use ruma::{ MilliSecondsSinceUnixEpoch, OwnedDeviceId, @@ -232,7 +232,7 @@ pub(crate) async fn delete_devices_route( .await?; if !worked { - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); } // Success! }, @@ -243,10 +243,10 @@ pub(crate) async fn delete_devices_route( .uiaa .create(sender_user, sender_device, &uiaainfo, json); - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); }, | _ => { - return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); + return Err!(BadRequest(ErrorKind::NotJson, "Not json.")); }, }, } diff --git a/src/api/client/keys.rs b/src/api/client/keys.rs index 7115a84d..e4c78078 100644 --- a/src/api/client/keys.rs +++ b/src/api/client/keys.rs @@ -5,7 +5,7 @@ use std::{ use axum::extract::State; use conduwuit::{ - Err, Error, Result, debug, debug_warn, err, + Err, Result, debug, debug_warn, err, result::NotFound, utils, utils::{IterStream, stream::WidebandExt}, @@ -215,7 +215,7 @@ pub(crate) async fn upload_signing_keys_route( .await?; if !worked { - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); } // Success! }, @@ -226,10 +226,10 @@ pub(crate) async fn upload_signing_keys_route( .uiaa .create(sender_user, sender_device, &uiaainfo, json); - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); }, | _ => { - return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); + return Err!(BadRequest(ErrorKind::NotJson, "Not json.")); }, }, } @@ -396,12 +396,12 @@ pub(crate) async fn get_key_changes_route( let from = body .from .parse() - .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?; + .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `from`.")))?; let to = body .to .parse() - .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?; + .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `to`.")))?; device_list_updates.extend( services diff --git a/src/api/client/media.rs b/src/api/client/media.rs index ce5b9ac0..404fa737 100644 --- a/src/api/client/media.rs +++ b/src/api/client/media.rs @@ -3,7 +3,7 @@ use std::time::Duration; use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduwuit::{ - Err, Result, err, + Err, Result, err, error, utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize}, }; use conduwuit_service::{ @@ -69,7 +69,7 @@ pub(crate) async fn create_content_route( .create(mxc, Some(user), Some(&content_disposition), content_type, &body.file) .await { - err!("Failed to save uploaded media: {e}"); + error!("Failed to save uploaded media: {e}"); return Err!(Request(Unknown("Failed to save uploaded media"))); } diff --git a/src/api/client/message.rs b/src/api/client/message.rs index 85187477..64fa633f 100644 --- a/src/api/client/message.rs +++ b/src/api/client/message.rs @@ -1,7 +1,7 @@ use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduwuit::{ - Err, Error, Result, at, debug_warn, + Err, Result, at, debug_warn, matrix::{ event::{Event, Matches}, pdu::PduCount, @@ -322,7 +322,7 @@ where if server_ignored { // the sender's server is ignored, so ignore this event - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::SenderIgnored { sender: None }, "The sender's server is ignored by this server.", )); @@ -331,7 +331,7 @@ where if user_ignored && !services.config.send_messages_from_ignored_users_to_client { // the recipient of this PDU has the sender ignored, and we're not // configured to send ignored messages to clients - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) }, "You have ignored this sender.", )); diff --git a/src/api/client/push.rs b/src/api/client/push.rs index d8d84ec7..6be58077 100644 --- a/src/api/client/push.rs +++ b/src/api/client/push.rs @@ -1,5 +1,5 @@ use axum::extract::State; -use conduwuit::{Err, Error, Result, err}; +use conduwuit::{Err, Result, err}; use conduwuit_service::Services; use ruma::{ CanonicalJsonObject, CanonicalJsonValue, @@ -243,27 +243,27 @@ pub(crate) async fn set_pushrule_route( body.before.as_deref(), ) { let err = match error { - | InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest( + | InsertPushRuleError::ServerDefaultRuleId => err!(BadRequest( ErrorKind::InvalidParam, "Rule IDs starting with a dot are reserved for server-default rules.", - ), - | InsertPushRuleError::InvalidRuleId => Error::BadRequest( + )), + | InsertPushRuleError::InvalidRuleId => err!(BadRequest( ErrorKind::InvalidParam, "Rule ID containing invalid characters.", - ), - | InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest( + )), + | InsertPushRuleError::RelativeToServerDefaultRule => err!(BadRequest( ErrorKind::InvalidParam, "Can't place a push rule relatively to a server-default rule.", - ), - | InsertPushRuleError::UnknownRuleId => Error::BadRequest( + )), + | InsertPushRuleError::UnknownRuleId => err!(BadRequest( ErrorKind::NotFound, "The before or after rule could not be found.", - ), - | InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest( + )), + | InsertPushRuleError::BeforeHigherThanAfter => err!(BadRequest( ErrorKind::InvalidParam, "The before rule has a higher priority than the after rule.", - ), - | _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."), + )), + | _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")), }; return Err(err); @@ -433,13 +433,13 @@ pub(crate) async fn delete_pushrule_route( .remove(body.kind.clone(), &body.rule_id) { let err = match error { - | RemovePushRuleError::ServerDefault => Error::BadRequest( + | RemovePushRuleError::ServerDefault => err!(BadRequest( ErrorKind::InvalidParam, "Cannot delete a server-default pushrule.", - ), + )), | RemovePushRuleError::NotFound => - Error::BadRequest(ErrorKind::NotFound, "Push rule not found."), - | _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."), + err!(BadRequest(ErrorKind::NotFound, "Push rule not found.")), + | _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")), }; return Err(err); diff --git a/src/api/client/room/upgrade.rs b/src/api/client/room/upgrade.rs index 1088393a..4b20571b 100644 --- a/src/api/client/room/upgrade.rs +++ b/src/api/client/room/upgrade.rs @@ -2,7 +2,7 @@ use std::cmp::max; use axum::extract::State; use conduwuit::{ - Err, Error, Event, Result, RoomVersion, debug, err, info, + Err, Event, Result, RoomVersion, debug, err, info, matrix::{StateKey, pdu::PduBuilder}, }; use futures::{FutureExt, StreamExt}; @@ -58,7 +58,7 @@ pub(crate) async fn upgrade_room_route( let sender_user = body.sender_user.as_ref().expect("user is authenticated"); if !services.server.supported_room_version(&body.new_version) { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::UnsupportedRoomVersion, "This server does not support that room version.", )); @@ -170,7 +170,7 @@ pub(crate) async fn upgrade_room_route( "creator".into(), json!(&sender_user).try_into().map_err(|e| { info!("Error forming creation event: {e}"); - Error::BadRequest(ErrorKind::BadJson, "Error forming creation event") + err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")) })?, ); }, @@ -186,13 +186,13 @@ pub(crate) async fn upgrade_room_route( "room_version".into(), json!(&body.new_version) .try_into() - .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, + .map_err(|_| err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")))?, ); create_event_content.insert( "predecessor".into(), json!(predecessor) .try_into() - .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, + .map_err(|_| err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")))?, ); // Validate creation event content @@ -203,7 +203,7 @@ pub(crate) async fn upgrade_room_route( ) .is_err() { - return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")); + return Err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")); } let create_event_id = services diff --git a/src/api/client/session.rs b/src/api/client/session.rs index f9afc266..42793430 100644 --- a/src/api/client/session.rs +++ b/src/api/client/session.rs @@ -3,7 +3,7 @@ use std::time::Duration; use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduwuit::{ - Err, Error, Result, debug, err, info, + Err, Result, debug, err, info, utils::{self, ReadyExt, hash}, warn, }; @@ -191,7 +191,7 @@ pub(crate) async fn handle_login( } if services.users.is_locked(&user_id).await? { - return Err(Error::BadRequest(ErrorKind::UserLocked, "This account has been locked.")); + return Err!(BadRequest(ErrorKind::UserLocked, "This account has been locked.")); } if services.users.is_login_disabled(&user_id).await { @@ -390,7 +390,7 @@ pub(crate) async fn login_token_route( .await?; if !worked { - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); } // Success! @@ -402,7 +402,7 @@ pub(crate) async fn login_token_route( .uiaa .create(sender_user, sender_device, &uiaainfo, json); - return Err(Error::Uiaa(uiaainfo)); + return Err!(Uiaa(uiaainfo)); }, | _ => { return Err!(Request(NotJson("No JSON body was sent when required."))); diff --git a/src/api/client/to_device.rs b/src/api/client/to_device.rs index 581f4a72..75dc45b4 100644 --- a/src/api/client/to_device.rs +++ b/src/api/client/to_device.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use axum::extract::State; -use conduwuit::{Error, Result}; +use conduwuit::{Result, err}; use conduwuit_service::sending::EduBuf; use futures::StreamExt; use ruma::{ @@ -66,7 +66,7 @@ pub(crate) async fn send_event_to_device_route( let event = event .deserialize_as() - .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid"))?; + .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Event is invalid")))?; match target_device_id_maybe { | DeviceIdOrAllDevices::DeviceId(target_device_id) => { diff --git a/src/api/client/well_known.rs b/src/api/client/well_known.rs index 95bf7c05..003f23f2 100644 --- a/src/api/client/well_known.rs +++ b/src/api/client/well_known.rs @@ -1,11 +1,8 @@ use axum::{Json, extract::State, response::IntoResponse}; -use conduwuit::{Error, Result}; -use ruma::api::client::{ - discovery::{ - discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo}, - discover_support::{self, Contact}, - }, - error::ErrorKind, +use conduwuit::{Err, Result}; +use ruma::api::client::discovery::{ + discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo}, + discover_support::{self, Contact}, }; use crate::Ruma; @@ -19,7 +16,7 @@ pub(crate) async fn well_known_client( ) -> Result { let client_url = match services.config.well_known.client.as_ref() { | Some(url) => url.to_string(), - | None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), + | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")), }; Ok(discover_homeserver::Response { @@ -88,7 +85,7 @@ pub(crate) async fn well_known_support( if contacts.is_empty() && support_page.is_none() { // No admin room, no configured contacts, and no support page - return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")); + return Err!(BadRequest(ErrorKind::NotFound, "Not found.")); } Ok(discover_support::Response { contacts, support_page }) @@ -105,7 +102,7 @@ pub(crate) async fn syncv3_client_server_json( | Some(url) => url.to_string(), | None => match services.config.well_known.server.as_ref() { | Some(url) => url.to_string(), - | None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), + | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")), }, }; diff --git a/src/api/router/auth.rs b/src/api/router/auth.rs index 60f8860f..bda0cb17 100644 --- a/src/api/router/auth.rs +++ b/src/api/router/auth.rs @@ -4,7 +4,7 @@ use axum_extra::{ headers::{Authorization, authorization::Bearer}, typed_header::TypedHeaderRejectionReason, }; -use conduwuit::{Err, Error, Result, debug_error, err, warn}; +use conduwuit::{Err, Result, debug_error, err, warn}; use futures::{ TryFutureExt, future::{ @@ -77,7 +77,7 @@ pub(super) async fn auth( // already }, | Token::None | Token::Invalid => { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::MissingToken, "Missing or invalid access token.", )); @@ -96,7 +96,7 @@ pub(super) async fn auth( // already }, | Token::None | Token::Invalid => { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::MissingToken, "Missing or invalid access token.", )); @@ -130,10 +130,10 @@ pub(super) async fn auth( appservice_info: None, }) } else { - Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")) + Err!(BadRequest(ErrorKind::MissingToken, "Missing access token.")) } }, - | _ => Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")), + | _ => Err!(BadRequest(ErrorKind::MissingToken, "Missing access token.")), }, | ( AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None, @@ -149,7 +149,7 @@ pub(super) async fn auth( &ruma::api::client::session::logout::v3::Request::METADATA | &ruma::api::client::session::logout_all::v3::Request::METADATA ) { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::UserLocked, "This account has been locked.", )); @@ -174,11 +174,11 @@ pub(super) async fn auth( appservice_info: None, }), | (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) => - Err(Error::BadRequest( + Err!(BadRequest( ErrorKind::Unauthorized, "Only server signatures should be used on this endpoint.", )), - | (AuthScheme::AppserviceToken, Token::User(_)) => Err(Error::BadRequest( + | (AuthScheme::AppserviceToken, Token::User(_)) => Err!(BadRequest( ErrorKind::Unauthorized, "Only appservice access tokens should be used on this endpoint.", )), @@ -196,13 +196,13 @@ pub(super) async fn auth( appservice_info: None, }) } else { - Err(Error::BadRequest( + Err!(BadRequest( ErrorKind::UnknownToken { soft_logout: false }, "Unknown access token.", )) } }, - | (_, Token::Invalid) => Err(Error::BadRequest( + | (_, Token::Invalid) => Err!(BadRequest( ErrorKind::UnknownToken { soft_logout: false }, "Unknown access token.", )), diff --git a/src/api/server/event_auth.rs b/src/api/server/event_auth.rs index a9019e8e..3064fb09 100644 --- a/src/api/server/event_auth.rs +++ b/src/api/server/event_auth.rs @@ -1,12 +1,9 @@ use std::{borrow::Borrow, iter::once}; use axum::extract::State; -use conduwuit::{Err, Error, Result, info, utils::stream::ReadyExt}; +use conduwuit::{Err, Error, Result, err, info, utils::stream::ReadyExt}; use futures::StreamExt; -use ruma::{ - RoomId, - api::{client::error::ErrorKind, federation::authorization::get_event_authorization}, -}; +use ruma::{RoomId, api::federation::authorization::get_event_authorization}; use super::AccessCheck; use crate::Ruma; @@ -47,7 +44,7 @@ pub(crate) async fn get_event_authorization_route( .timeline .get_pdu_json(&body.event_id) .await - .map_err(|_| Error::BadRequest(ErrorKind::NotFound, "Event not found."))?; + .map_err(|_| err!(BadRequest(ErrorKind::NotFound, "Event not found.")))?; let room_id_str = event .get("room_id") diff --git a/src/api/server/invite.rs b/src/api/server/invite.rs index beb0c488..b451fe66 100644 --- a/src/api/server/invite.rs +++ b/src/api/server/invite.rs @@ -2,7 +2,7 @@ use axum::extract::State; use axum_client_ip::InsecureClientIp; use base64::{Engine as _, engine::general_purpose}; use conduwuit::{ - Err, Error, PduEvent, Result, err, error, + Err, PduEvent, Result, err, error, matrix::{Event, event::gen_event_id}, utils::{self, hash::sha256}, warn, @@ -33,7 +33,7 @@ pub(crate) async fn create_invite_route( .await?; if !services.server.supported_room_version(&body.room_version) { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() }, "Server does not support this room version.", )); diff --git a/src/api/server/make_join.rs b/src/api/server/make_join.rs index cc3accb1..94735afe 100644 --- a/src/api/server/make_join.rs +++ b/src/api/server/make_join.rs @@ -1,7 +1,7 @@ use std::borrow::ToOwned; use axum::extract::State; -use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn}; +use conduwuit::{Err, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn}; use conduwuit_service::Services; use futures::StreamExt; use ruma::{ @@ -80,7 +80,7 @@ pub(crate) async fn create_join_event_template_route( let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?; if !body.ver.contains(&room_version_id) { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, "Room version not supported.", )); diff --git a/src/api/server/make_knock.rs b/src/api/server/make_knock.rs index 83fd0081..9e8dd670 100644 --- a/src/api/server/make_knock.rs +++ b/src/api/server/make_knock.rs @@ -1,6 +1,6 @@ use RoomVersionId::*; use axum::extract::State; -use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PduBuilder, warn}; +use conduwuit::{Err, Result, debug_warn, info, matrix::pdu::PduBuilder, warn}; use ruma::{ RoomVersionId, api::{client::error::ErrorKind, federation::knock::create_knock_event_template}, @@ -67,14 +67,14 @@ pub(crate) async fn create_knock_event_template_route( let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?; if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, "Room version does not support knocking.", )); } if !body.ver.contains(&room_version_id) { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, "Your homeserver does not support the features required to knock on this room.", )); diff --git a/src/api/server/publicrooms.rs b/src/api/server/publicrooms.rs index cf66ea71..1bde7601 100644 --- a/src/api/server/publicrooms.rs +++ b/src/api/server/publicrooms.rs @@ -1,6 +1,6 @@ use axum::extract::State; use axum_client_ip::InsecureClientIp; -use conduwuit::{Error, Result}; +use conduwuit::{Err, Result, err}; use ruma::{ api::{ client::error::ErrorKind, @@ -25,7 +25,7 @@ pub(crate) async fn get_public_rooms_filtered_route( .config .allow_public_room_directory_over_federation { - return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public")); + return Err!(BadRequest(ErrorKind::forbidden(), "Room directory is not public")); } let response = crate::client::get_public_rooms_filtered_helper( @@ -38,7 +38,10 @@ pub(crate) async fn get_public_rooms_filtered_route( ) .await .map_err(|_| { - Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.") + err!(BadRequest( + ErrorKind::Unknown, + "Failed to return this server's public room list." + )) })?; Ok(get_public_rooms_filtered::v1::Response { @@ -62,7 +65,7 @@ pub(crate) async fn get_public_rooms_route( .globals .allow_public_room_directory_over_federation() { - return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public")); + return Err!(BadRequest(ErrorKind::forbidden(), "Room directory is not public")); } let response = crate::client::get_public_rooms_filtered_helper( @@ -75,7 +78,10 @@ pub(crate) async fn get_public_rooms_route( ) .await .map_err(|_| { - Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.") + err!(BadRequest( + ErrorKind::Unknown, + "Failed to return this server's public room list." + )) })?; Ok(get_public_rooms::v1::Response { diff --git a/src/api/server/query.rs b/src/api/server/query.rs index 9124eee9..92081329 100644 --- a/src/api/server/query.rs +++ b/src/api/server/query.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use axum::extract::State; -use conduwuit::{Error, Result, err}; +use conduwuit::{Err, Result, err}; use futures::StreamExt; use get_profile_information::v1::ProfileField; use rand::seq::SliceRandom; @@ -67,17 +67,16 @@ pub(crate) async fn get_profile_information_route( .config .allow_inbound_profile_lookup_federation_requests { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::forbidden(), "Profile lookup over federation is not allowed on this homeserver.", )); } if !services.globals.server_is_ours(body.user_id.server_name()) { - return Err(Error::BadRequest( - ErrorKind::InvalidParam, - "User does not belong to this server.", - )); + return Err!( + BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",) + ); } let mut displayname = None; diff --git a/src/api/server/send.rs b/src/api/server/send.rs index 58a8c82b..3bea730d 100644 --- a/src/api/server/send.rs +++ b/src/api/server/send.rs @@ -114,7 +114,7 @@ pub(crate) async fn send_transaction_message_route( ); for (id, result) in &results { if let Err(e) = result { - if matches!(e, Error::BadRequest(ErrorKind::NotFound, _)) { + if matches!(e, Error::BadRequest { kind: ErrorKind::NotFound, .. }) { warn!("Incoming PDU failed {id}: {e:?}"); } } diff --git a/src/api/server/user.rs b/src/api/server/user.rs index 3c00bdad..4fc63349 100644 --- a/src/api/server/user.rs +++ b/src/api/server/user.rs @@ -1,7 +1,7 @@ use std::time::Duration; use axum::extract::State; -use conduwuit::{Error, Result}; +use conduwuit::{Err, Result}; use futures::{FutureExt, StreamExt, TryFutureExt}; use ruma::api::{ client::error::ErrorKind, @@ -24,7 +24,7 @@ pub(crate) async fn get_devices_route( body: Ruma, ) -> Result { if !services.globals.user_is_local(&body.user_id) { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::InvalidParam, "Tried to access user from other server.", )); @@ -86,10 +86,9 @@ pub(crate) async fn get_keys_route( .iter() .any(|(u, _)| !services.globals.user_is_local(u)) { - return Err(Error::BadRequest( - ErrorKind::InvalidParam, - "User does not belong to this server.", - )); + return Err!( + BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",) + ); } let result = get_keys_helper( @@ -121,7 +120,7 @@ pub(crate) async fn claim_keys_route( .iter() .any(|(u, _)| !services.globals.user_is_local(u)) { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::InvalidParam, "Tried to access user from other server.", )); diff --git a/src/api/server/well_known.rs b/src/api/server/well_known.rs index 75c7cf5d..954eee4d 100644 --- a/src/api/server/well_known.rs +++ b/src/api/server/well_known.rs @@ -1,6 +1,6 @@ use axum::extract::State; -use conduwuit::{Error, Result}; -use ruma::api::{client::error::ErrorKind, federation::discovery::discover_homeserver}; +use conduwuit::{Err, Result}; +use ruma::api::federation::discovery::discover_homeserver; use crate::Ruma; @@ -14,7 +14,7 @@ pub(crate) async fn well_known_server( Ok(discover_homeserver::Response { server: match services.server.config.well_known.server.as_ref() { | Some(server_name) => server_name.to_owned(), - | None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), + | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")), }, }) } diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index 0b8b8692..e0e83539 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -98,7 +98,8 @@ serde-saphyr.workspace = true serde.workspace = true smallvec.workspace = true smallstr.workspace = true -thiserror.workspace = true +snafu.workspace = true +paste.workspace = true tikv-jemallocator.optional = true tikv-jemallocator.workspace = true tikv-jemalloc-ctl.optional = true diff --git a/src/core/error/err.rs b/src/core/error/err.rs index 314e03f3..e969f178 100644 --- a/src/core/error/err.rs +++ b/src/core/error/err.rs @@ -45,63 +45,162 @@ macro_rules! Err { macro_rules! err { (Request(Forbidden($level:ident!($($args:tt)+)))) => {{ let mut buf = String::new(); - $crate::error::Error::Request( - $crate::ruma::api::client::error::ErrorKind::forbidden(), - $crate::err_log!(buf, $level, $($args)+), - $crate::http::StatusCode::BAD_REQUEST - ) + $crate::error::Error::Request { + kind: $crate::ruma::api::client::error::ErrorKind::forbidden(), + message: $crate::err_log!(buf, $level, $($args)+), + code: $crate::http::StatusCode::BAD_REQUEST, + backtrace: Some($crate::snafu::Backtrace::capture()), + } }}; (Request(Forbidden($($args:tt)+))) => { - $crate::error::Error::Request( - $crate::ruma::api::client::error::ErrorKind::forbidden(), - $crate::format_maybe!($($args)+), - $crate::http::StatusCode::BAD_REQUEST - ) + { + let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+); + $crate::error::Error::Request { + kind: $crate::ruma::api::client::error::ErrorKind::forbidden(), + message, + code: $crate::http::StatusCode::BAD_REQUEST, + backtrace: Some($crate::snafu::Backtrace::capture()), + } + } + }; + + (Request(NotFound($level:ident!($($args:tt)+)))) => {{ + let mut buf = String::new(); + $crate::error::Error::Request { + kind: $crate::ruma::api::client::error::ErrorKind::NotFound, + message: $crate::err_log!(buf, $level, $($args)+), + code: $crate::http::StatusCode::BAD_REQUEST, + backtrace: None, + } + }}; + + (Request(NotFound($($args:tt)+))) => { + { + let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+); + $crate::error::Error::Request { + kind: $crate::ruma::api::client::error::ErrorKind::NotFound, + message, + code: $crate::http::StatusCode::BAD_REQUEST, + backtrace: None, + } + } }; (Request($variant:ident($level:ident!($($args:tt)+)))) => {{ let mut buf = String::new(); - $crate::error::Error::Request( - $crate::ruma::api::client::error::ErrorKind::$variant, - $crate::err_log!(buf, $level, $($args)+), - $crate::http::StatusCode::BAD_REQUEST - ) + $crate::error::Error::Request { + kind: $crate::ruma::api::client::error::ErrorKind::$variant, + message: $crate::err_log!(buf, $level, $($args)+), + code: $crate::http::StatusCode::BAD_REQUEST, + backtrace: Some($crate::snafu::Backtrace::capture()), + } }}; (Request($variant:ident($($args:tt)+))) => { - $crate::error::Error::Request( - $crate::ruma::api::client::error::ErrorKind::$variant, - $crate::format_maybe!($($args)+), - $crate::http::StatusCode::BAD_REQUEST - ) + { + let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+); + $crate::error::Error::Request { + kind: $crate::ruma::api::client::error::ErrorKind::$variant, + message, + code: $crate::http::StatusCode::BAD_REQUEST, + backtrace: Some($crate::snafu::Backtrace::capture()), + } + } }; (Config($item:literal, $($args:tt)+)) => {{ let mut buf = String::new(); - $crate::error::Error::Config($item, $crate::err_log!(buf, error, config = %$item, $($args)+)) + $crate::error::ConfigSnafu { + directive: $item, + message: $crate::err_log!(buf, error, config = %$item, $($args)+), + }.build() }}; + (BadRequest(ErrorKind::NotFound, $($args:tt)+)) => { + { + let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+); + $crate::error::Error::Request { + kind: $crate::ruma::api::client::error::ErrorKind::NotFound, + message, + code: $crate::http::StatusCode::BAD_REQUEST, + backtrace: None, + } + } + }; + + (BadRequest($kind:expr, $($args:tt)+)) => { + { + let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+); + $crate::error::BadRequestSnafu { + kind: $kind, + message, + }.build() + } + }; + + (FeatureDisabled($($args:tt)+)) => { + { + let feature: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+); + $crate::error::FeatureDisabledSnafu { feature }.build() + } + }; + + (Federation($server:expr, $error:expr $(,)?)) => { + { + $crate::error::FederationSnafu { + server: $server, + error: $error, + }.build() + } + }; + + (InconsistentRoomState($message:expr, $room_id:expr $(,)?)) => { + { + $crate::error::InconsistentRoomStateSnafu { + message: $message, + room_id: $room_id, + }.build() + } + }; + + (Uiaa($info:expr $(,)?)) => { + { + $crate::error::UiaaSnafu { + info: $info, + }.build() + } + }; + ($variant:ident($level:ident!($($args:tt)+))) => {{ let mut buf = String::new(); - $crate::error::Error::$variant($crate::err_log!(buf, $level, $($args)+)) + $crate::paste::paste! { + $crate::error::[<$variant Snafu>] { + message: $crate::err_log!(buf, $level, $($args)+), + }.build() + } }}; - ($variant:ident($($args:ident),+)) => { - $crate::error::Error::$variant($($args),+) - }; - ($variant:ident($($args:tt)+)) => { - $crate::error::Error::$variant($crate::format_maybe!($($args)+)) + $crate::paste::paste! { + { + let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+); + $crate::error::[<$variant Snafu>] { message }.build() + } + } }; ($level:ident!($($args:tt)+)) => {{ let mut buf = String::new(); - $crate::error::Error::Err($crate::err_log!(buf, $level, $($args)+)) + let message: std::borrow::Cow<'static, str> = $crate::err_log!(buf, $level, $($args)+); + $crate::error::ErrSnafu { message }.build() }}; ($($args:tt)+) => { - $crate::error::Error::Err($crate::format_maybe!($($args)+)) + { + let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+); + $crate::error::ErrSnafu { message }.build() + } }; } @@ -134,7 +233,7 @@ macro_rules! err_log { }; ($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset_all!(__CALLSITE.metadata().fields(), $($fields)+)); - ($out).into() + std::borrow::Cow::<'static, str>::from($out) }} } diff --git a/src/core/error/mod.rs b/src/core/error/mod.rs index 03967e62..04850a20 100644 --- a/src/core/error/mod.rs +++ b/src/core/error/mod.rs @@ -6,151 +6,391 @@ mod serde; use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError}; +use snafu::{IntoError, prelude::*}; + pub use self::{err::visit, log::*}; -#[derive(thiserror::Error)] +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] pub enum Error { - #[error("PANIC!")] - PanicAny(Box), - #[error("PANIC! {0}")] - Panic(&'static str, Box), + #[snafu(display("PANIC!"))] + PanicAny { + panic: Box, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("PANIC! {message}"))] + Panic { + message: &'static str, + panic: Box, + backtrace: snafu::Backtrace, + }, // std - #[error(transparent)] - Fmt(#[from] std::fmt::Error), - #[error(transparent)] - FromUtf8(#[from] std::string::FromUtf8Error), - #[error("I/O error: {0}")] - Io(#[from] std::io::Error), - #[error(transparent)] - ParseFloat(#[from] std::num::ParseFloatError), - #[error(transparent)] - ParseInt(#[from] std::num::ParseIntError), - #[error(transparent)] - Std(#[from] Box), - #[error(transparent)] - ThreadAccessError(#[from] std::thread::AccessError), - #[error(transparent)] - TryFromInt(#[from] std::num::TryFromIntError), - #[error(transparent)] - TryFromSlice(#[from] std::array::TryFromSliceError), - #[error(transparent)] - Utf8(#[from] std::str::Utf8Error), + #[snafu(display("Format error: {source}"))] + Fmt { + source: std::fmt::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("UTF-8 conversion error: {source}"))] + FromUtf8 { + source: std::string::FromUtf8Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("I/O error: {source}"))] + Io { + source: std::io::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Parse float error: {source}"))] + ParseFloat { + source: std::num::ParseFloatError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Parse int error: {source}"))] + ParseInt { + source: std::num::ParseIntError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Error: {source}"))] + Std { + source: Box, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Thread access error: {source}"))] + ThreadAccessError { + source: std::thread::AccessError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Integer conversion error: {source}"))] + TryFromInt { + source: std::num::TryFromIntError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Slice conversion error: {source}"))] + TryFromSlice { + source: std::array::TryFromSliceError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("UTF-8 error: {source}"))] + Utf8 { + source: std::str::Utf8Error, + backtrace: snafu::Backtrace, + }, // third-party - #[error(transparent)] - CapacityError(#[from] arrayvec::CapacityError), - #[error(transparent)] - CargoToml(#[from] cargo_toml::Error), - #[error(transparent)] - Clap(#[from] clap::error::Error), - #[error(transparent)] - Extension(#[from] axum::extract::rejection::ExtensionRejection), - #[error(transparent)] - Figment(#[from] figment::error::Error), - #[error(transparent)] - Http(#[from] http::Error), - #[error(transparent)] - HttpHeader(#[from] http::header::InvalidHeaderValue), - #[error("Join error: {0}")] - JoinError(#[from] tokio::task::JoinError), - #[error(transparent)] - Json(#[from] serde_json::Error), - #[error(transparent)] - JsParseInt(#[from] ruma::JsParseIntError), // js_int re-export - #[error(transparent)] - JsTryFromInt(#[from] ruma::JsTryFromIntError), // js_int re-export - #[error(transparent)] - Path(#[from] axum::extract::rejection::PathRejection), - #[error("Mutex poisoned: {0}")] - Poison(Cow<'static, str>), - #[error("Regex error: {0}")] - Regex(#[from] regex::Error), - #[error("Request error: {0}")] - Reqwest(#[from] reqwest::Error), - #[error("{0}")] - SerdeDe(Cow<'static, str>), - #[error("{0}")] - SerdeSer(Cow<'static, str>), - #[error(transparent)] - TomlDe(#[from] toml::de::Error), - #[error(transparent)] - TomlSer(#[from] toml::ser::Error), - #[error("Tracing filter error: {0}")] - TracingFilter(#[from] tracing_subscriber::filter::ParseError), - #[error("Tracing reload error: {0}")] - TracingReload(#[from] tracing_subscriber::reload::Error), - #[error(transparent)] - TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection), - #[error(transparent)] - YamlDe(#[from] serde_saphyr::Error), - #[error(transparent)] - YamlSer(#[from] serde_saphyr::ser_error::Error), + #[snafu(display("Capacity error: {source}"))] + CapacityError { + source: arrayvec::CapacityError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Cargo.toml error: {source}"))] + CargoToml { + source: cargo_toml::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Clap error: {source}"))] + Clap { + source: clap::error::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Extension rejection: {source}"))] + Extension { + source: axum::extract::rejection::ExtensionRejection, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Figment error: {source}"))] + Figment { + source: figment::error::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("HTTP error: {source}"))] + Http { + source: http::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Invalid HTTP header value: {source}"))] + HttpHeader { + source: http::header::InvalidHeaderValue, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Join error: {source}"))] + JoinError { + source: tokio::task::JoinError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("JSON error: {source}"))] + Json { + source: serde_json::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("JS parse int error: {source}"))] + JsParseInt { + source: ruma::JsParseIntError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("JS try from int error: {source}"))] + JsTryFromInt { + source: ruma::JsTryFromIntError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Path rejection: {source}"))] + Path { + source: axum::extract::rejection::PathRejection, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Mutex poisoned: {message}"))] + Poison { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Regex error: {source}"))] + Regex { + source: regex::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Request error: {source}"))] + Reqwest { + source: reqwest::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{message}"))] + SerdeDe { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{message}"))] + SerdeSer { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("TOML deserialization error: {source}"))] + TomlDe { + source: toml::de::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("TOML serialization error: {source}"))] + TomlSer { + source: toml::ser::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Tracing filter error: {source}"))] + TracingFilter { + source: tracing_subscriber::filter::ParseError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Tracing reload error: {source}"))] + TracingReload { + source: tracing_subscriber::reload::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Typed header rejection: {source}"))] + TypedHeader { + source: axum_extra::typed_header::TypedHeaderRejection, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("YAML deserialization error: {source}"))] + YamlDe { + source: serde_saphyr::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("YAML serialization error: {source}"))] + YamlSer { + source: serde_saphyr::ser_error::Error, + backtrace: snafu::Backtrace, + }, // ruma/conduwuit - #[error("Arithmetic operation failed: {0}")] - Arithmetic(Cow<'static, str>), - #[error("{0}: {1}")] - BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove - #[error("{0}")] - BadServerResponse(Cow<'static, str>), - #[error(transparent)] - CanonicalJson(#[from] ruma::CanonicalJsonError), - #[error("There was a problem with the '{0}' directive in your configuration: {1}")] - Config(&'static str, Cow<'static, str>), - #[error("{0}")] - Conflict(Cow<'static, str>), // This is only needed for when a room alias already exists - #[error(transparent)] - ContentDisposition(#[from] ruma::http_headers::ContentDispositionParseError), - #[error("{0}")] - Database(Cow<'static, str>), - #[error("Feature '{0}' is not available on this server.")] - FeatureDisabled(Cow<'static, str>), - #[error("Remote server {0} responded with: {1}")] - Federation(ruma::OwnedServerName, ruma::api::client::error::Error), - #[error("{0} in {1}")] - InconsistentRoomState(&'static str, ruma::OwnedRoomId), - #[error(transparent)] - IntoHttp(#[from] ruma::api::error::IntoHttpError), - #[error("{0}")] - Ldap(Cow<'static, str>), - #[error(transparent)] - Mxc(#[from] ruma::MxcUriError), - #[error(transparent)] - Mxid(#[from] ruma::IdParseError), - #[error("from {0}: {1}")] - Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError), - #[error("{0}: {1}")] - Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode), - #[error(transparent)] - Ruma(#[from] ruma::api::client::error::Error), - #[error(transparent)] - Signatures(#[from] ruma::signatures::Error), - #[error(transparent)] - StateRes(#[from] crate::state_res::Error), - #[error("uiaa")] - Uiaa(ruma::api::client::uiaa::UiaaInfo), + #[snafu(display("Arithmetic operation failed: {message}"))] + Arithmetic { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{kind}: {message}"))] + BadRequest { + kind: ruma::api::client::error::ErrorKind, + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{message}"))] + BadServerResponse { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Canonical JSON error: {source}"))] + CanonicalJson { + source: ruma::CanonicalJsonError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display( + "There was a problem with the '{directive}' directive in your configuration: {message}" + ))] + Config { + directive: &'static str, + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{message}"))] + Conflict { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Content disposition error: {source}"))] + ContentDisposition { + source: ruma::http_headers::ContentDispositionParseError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{message}"))] + Database { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Feature '{feature}' is not available on this server."))] + FeatureDisabled { + feature: Cow<'static, str>, + }, + + #[snafu(display("Remote server {server} responded with: {error}"))] + Federation { + server: ruma::OwnedServerName, + error: ruma::api::client::error::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{message} in {room_id}"))] + InconsistentRoomState { + message: &'static str, + room_id: ruma::OwnedRoomId, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("HTTP conversion error: {source}"))] + IntoHttp { + source: ruma::api::error::IntoHttpError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{message}"))] + Ldap { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("MXC URI error: {source}"))] + Mxc { + source: ruma::MxcUriError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Matrix ID parse error: {source}"))] + Mxid { + source: ruma::IdParseError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("from {server}: {error}"))] + Redaction { + server: ruma::OwnedServerName, + error: ruma::canonical_json::RedactionError, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("{kind}: {message}"))] + Request { + kind: ruma::api::client::error::ErrorKind, + message: Cow<'static, str>, + code: http::StatusCode, + backtrace: Option, + }, + + #[snafu(display("Ruma error: {source}"))] + Ruma { + source: ruma::api::client::error::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("Signature error: {source}"))] + Signatures { + source: ruma::signatures::Error, + backtrace: snafu::Backtrace, + }, + + #[snafu(display("State resolution error: {source}"))] + #[snafu(context(false))] + StateRes { + source: crate::state_res::Error, + }, + + #[snafu(display("uiaa"))] + Uiaa { + info: ruma::api::client::uiaa::UiaaInfo, + }, // unique / untyped - #[error("{0}")] - Err(Cow<'static, str>), + #[snafu(display("{message}"))] + Err { + message: Cow<'static, str>, + backtrace: snafu::Backtrace, + }, } impl Error { #[inline] #[must_use] - pub fn from_errno() -> Self { Self::Io(std::io::Error::last_os_error()) } + pub fn from_errno() -> Self { IoSnafu {}.into_error(std::io::Error::last_os_error()) } //#[deprecated] + #[must_use] pub fn bad_database(message: &'static str) -> Self { - crate::err!(Database(error!("{message}"))) + let message: Cow<'static, str> = message.into(); + DatabaseSnafu { message }.build() } /// Sanitizes public-facing errors that can leak sensitive information. pub fn sanitized_message(&self) -> String { match self { - | Self::Database(..) => String::from("Database error occurred."), - | Self::Io(..) => String::from("I/O error occurred."), + | Self::Database { .. } => String::from("Database error occurred."), + | Self::Io { .. } => String::from("I/O error occurred."), | _ => self.message(), } } @@ -158,8 +398,8 @@ impl Error { /// Generate the error message string. pub fn message(&self) -> String { match self { - | Self::Federation(origin, error) => format!("Answer from {origin}: {error}"), - | Self::Ruma(error) => response::ruma_error_message(error), + | Self::Federation { server, error, .. } => format!("Answer from {server}: {error}"), + | Self::Ruma { source, .. } => response::ruma_error_message(source), | _ => format!("{self}"), } } @@ -170,10 +410,10 @@ impl Error { use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown}; match self { - | Self::Federation(_, error) | Self::Ruma(error) => - response::ruma_error_kind(error).clone(), - | Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(), - | Self::FeatureDisabled(..) => FeatureDisabled, + | Self::Federation { error, .. } => response::ruma_error_kind(error).clone(), + | Self::Ruma { source, .. } => response::ruma_error_kind(source).clone(), + | Self::BadRequest { kind, .. } | Self::Request { kind, .. } => kind.clone(), + | Self::FeatureDisabled { .. } => FeatureDisabled, | _ => Unknown, } } @@ -184,13 +424,15 @@ impl Error { use http::StatusCode; match self { - | Self::Federation(_, error) | Self::Ruma(error) => error.status_code, - | Self::Request(kind, _, code) => response::status_code(kind, *code), - | Self::BadRequest(kind, ..) => response::bad_request_code(kind), - | Self::FeatureDisabled(..) => response::bad_request_code(&self.kind()), - | Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), - | Self::Conflict(_) => StatusCode::CONFLICT, - | Self::Io(error) => response::io_error_code(error.kind()), + | Self::Federation { error, .. } => error.status_code, + | Self::Ruma { source, .. } => source.status_code, + | Self::Request { kind, code, .. } => response::status_code(kind, *code), + | Self::BadRequest { kind, .. } => response::bad_request_code(kind), + | Self::FeatureDisabled { .. } => response::bad_request_code(&self.kind()), + | Self::Reqwest { source, .. } => + source.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), + | Self::Conflict { .. } => StatusCode::CONFLICT, + | Self::Io { source, .. } => response::io_error_code(source.kind()), | _ => StatusCode::INTERNAL_SERVER_ERROR, } } @@ -203,16 +445,46 @@ impl Error { pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND } } -impl std::fmt::Debug for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message()) - } +// Debug is already derived by Snafu + +/// Macro to reduce boilerplate for From implementations using Snafu context +macro_rules! impl_from_snafu { + ($source_ty:ty => $context:ident) => { + impl From<$source_ty> for Error { + fn from(source: $source_ty) -> Self { $context.into_error(source) } + } + }; +} + +/// Macro for From impls that format messages into ErrSnafu or other +/// message-based contexts +macro_rules! impl_from_message { + ($source_ty:ty => $context:ident, $msg:expr) => { + impl From<$source_ty> for Error { + fn from(source: $source_ty) -> Self { + let message: Cow<'static, str> = format!($msg, source).into(); + $context { message }.build() + } + } + }; +} + +/// Macro for From impls with constant messages (no formatting) +macro_rules! impl_from_const_message { + ($source_ty:ty => $context:ident, $msg:expr) => { + impl From<$source_ty> for Error { + fn from(_source: $source_ty) -> Self { + let message: Cow<'static, str> = $msg.into(); + $context { message }.build() + } + } + }; } impl From> for Error { #[cold] #[inline(never)] - fn from(e: PoisonError) -> Self { Self::Poison(e.to_string().into()) } + fn from(e: PoisonError) -> Self { PoisonSnafu { message: e.to_string() }.build() } } #[allow(clippy::fallible_impl_from)] @@ -224,6 +496,43 @@ impl From for Error { } } +// Implementations using the macro +impl_from_snafu!(std::io::Error => IoSnafu); +impl_from_snafu!(std::string::FromUtf8Error => FromUtf8Snafu); +impl_from_snafu!(regex::Error => RegexSnafu); +impl_from_snafu!(ruma::http_headers::ContentDispositionParseError => ContentDispositionSnafu); +impl_from_snafu!(ruma::api::error::IntoHttpError => IntoHttpSnafu); +impl_from_snafu!(ruma::JsTryFromIntError => JsTryFromIntSnafu); +impl_from_snafu!(ruma::CanonicalJsonError => CanonicalJsonSnafu); +impl_from_snafu!(axum::extract::rejection::PathRejection => PathSnafu); +impl_from_snafu!(clap::error::Error => ClapSnafu); +impl_from_snafu!(ruma::MxcUriError => MxcSnafu); +impl_from_snafu!(serde_saphyr::ser_error::Error => YamlSerSnafu); +impl_from_snafu!(toml::de::Error => TomlDeSnafu); +impl_from_snafu!(http::header::InvalidHeaderValue => HttpHeaderSnafu); +impl_from_snafu!(serde_json::Error => JsonSnafu); + +// Custom implementations using message formatting +impl_from_const_message!(std::fmt::Error => ErrSnafu, "formatting error"); +impl_from_message!(std::str::Utf8Error => ErrSnafu, "UTF-8 error: {}"); +impl_from_message!(std::num::TryFromIntError => ArithmeticSnafu, "integer conversion error: {}"); +impl_from_message!(tracing_subscriber::reload::Error => ErrSnafu, "tracing reload error: {}"); +impl_from_message!(reqwest::Error => ErrSnafu, "HTTP client error: {}"); +impl_from_message!(ruma::signatures::Error => ErrSnafu, "Signature error: {}"); +impl_from_message!(ruma::IdParseError => ErrSnafu, "ID parse error: {}"); +impl_from_message!(std::num::ParseIntError => ErrSnafu, "Integer parse error: {}"); +impl_from_message!(std::array::TryFromSliceError => ErrSnafu, "Slice conversion error: {}"); +impl_from_message!(tokio::task::JoinError => ErrSnafu, "Task join error: {}"); +impl_from_message!(serde_saphyr::Error => ErrSnafu, "YAML error: {}"); + +// Generic implementation for CapacityError +impl From> for Error { + fn from(_source: arrayvec::CapacityError) -> Self { + let message: Cow<'static, str> = "capacity error: buffer is full".into(); + ErrSnafu { message }.build() + } +} + #[cold] #[inline(never)] pub fn infallible(_e: &Infallible) { diff --git a/src/core/error/panic.rs b/src/core/error/panic.rs index 2e63105b..d7da8f6c 100644 --- a/src/core/error/panic.rs +++ b/src/core/error/panic.rs @@ -15,13 +15,16 @@ impl Error { #[must_use] #[inline] - pub fn from_panic(e: Box) -> Self { Self::Panic(debug::panic_str(&e), e) } + pub fn from_panic(e: Box) -> Self { + use super::PanicSnafu; + PanicSnafu { message: debug::panic_str(&e), panic: e }.build() + } #[inline] pub fn into_panic(self) -> Box { match self { - | Self::Panic(_, e) | Self::PanicAny(e) => e, - | Self::JoinError(e) => e.into_panic(), + | Self::Panic { panic, .. } | Self::PanicAny { panic, .. } => panic, + | Self::JoinError { source, .. } => source.into_panic(), | _ => Box::new(self), } } @@ -37,8 +40,8 @@ impl Error { #[inline] pub fn is_panic(&self) -> bool { match &self { - | Self::Panic(..) | Self::PanicAny(..) => true, - | Self::JoinError(e) => e.is_panic(), + | Self::Panic { .. } | Self::PanicAny { .. } => true, + | Self::JoinError { source, .. } => source.is_panic(), | _ => false, } } diff --git a/src/core/error/response.rs b/src/core/error/response.rs index d1f09a99..5972b47d 100644 --- a/src/core/error/response.rs +++ b/src/core/error/response.rs @@ -47,8 +47,8 @@ impl axum::response::IntoResponse for Error { impl From for UiaaResponse { #[inline] fn from(error: Error) -> Self { - if let Error::Uiaa(uiaainfo) = error { - return Self::AuthResponse(uiaainfo); + if let Error::Uiaa { info, .. } = error { + return Self::AuthResponse(info); } let body = ErrorBody::Standard { diff --git a/src/core/error/serde.rs b/src/core/error/serde.rs index 0c5a153b..04ee57b5 100644 --- a/src/core/error/serde.rs +++ b/src/core/error/serde.rs @@ -5,9 +5,15 @@ use serde::{de, ser}; use crate::Error; impl de::Error for Error { - fn custom(msg: T) -> Self { Self::SerdeDe(msg.to_string().into()) } + fn custom(msg: T) -> Self { + let message: std::borrow::Cow<'static, str> = msg.to_string().into(); + super::SerdeDeSnafu { message }.build() + } } impl ser::Error for Error { - fn custom(msg: T) -> Self { Self::SerdeSer(msg.to_string().into()) } + fn custom(msg: T) -> Self { + let message: std::borrow::Cow<'static, str> = msg.to_string().into(); + super::SerdeSerSnafu { message }.build() + } } diff --git a/src/core/matrix/pdu/redact.rs b/src/core/matrix/pdu/redact.rs index 896e03f8..5a591ba8 100644 --- a/src/core/matrix/pdu/redact.rs +++ b/src/core/matrix/pdu/redact.rs @@ -1,7 +1,7 @@ use ruma::{RoomVersionId, canonical_json::redact_content_in_place}; use serde_json::{Value as JsonValue, json, value::to_raw_value}; -use crate::{Error, Result, err, implement}; +use crate::{Result, err, implement}; #[implement(super::Pdu)] pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result { @@ -10,8 +10,15 @@ pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> let mut content = serde_json::from_str(self.content.get()) .map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?; - redact_content_in_place(&mut content, room_version_id, self.kind.to_string()) - .map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?; + redact_content_in_place(&mut content, room_version_id, self.kind.to_string()).map_err( + |error| { + crate::error::RedactionSnafu { + server: self.sender.server_name().to_owned(), + error, + } + .build() + }, + )?; let reason = serde_json::to_value(reason).expect("Failed to preserialize reason"); diff --git a/src/core/matrix/state_res/benches.rs b/src/core/matrix/state_res/benches.rs index de62f266..31d7b373 100644 --- a/src/core/matrix/state_res/benches.rs +++ b/src/core/matrix/state_res/benches.rs @@ -27,7 +27,7 @@ use serde_json::{ use crate::{ matrix::{Event, Pdu, pdu::EventHash}, - state_res::{self as state_res, Error, Result, StateMap}, + state_res::{self as state_res, Error, Result, StateMap, error::NotFoundSnafu}, }; static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0); @@ -170,10 +170,12 @@ struct TestStore(HashMap); #[allow(unused)] impl TestStore { fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { - self.0 - .get(event_id) - .cloned() - .ok_or_else(|| Error::NotFound(format!("{} not found", event_id))) + self.0.get(event_id).cloned().ok_or_else(|| { + NotFoundSnafu { + message: format!("{} not found", event_id), + } + .build() + }) } /// Returns the events that correspond to the `event_ids` sorted in the same diff --git a/src/core/matrix/state_res/error.rs b/src/core/matrix/state_res/error.rs index 7711d878..eb89375c 100644 --- a/src/core/matrix/state_res/error.rs +++ b/src/core/matrix/state_res/error.rs @@ -1,23 +1,40 @@ use serde_json::Error as JsonError; -use thiserror::Error; +use snafu::{IntoError, prelude::*}; /// Represents the various errors that arise when resolving state. -#[derive(Error, Debug)] +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] #[non_exhaustive] pub enum Error { /// A deserialization error. - #[error(transparent)] - SerdeJson(#[from] JsonError), + #[snafu(display("JSON error: {source}"))] + SerdeJson { + source: JsonError, + backtrace: snafu::Backtrace, + }, /// The given option or version is unsupported. - #[error("Unsupported room version: {0}")] - Unsupported(String), + #[snafu(display("Unsupported room version: {version}"))] + Unsupported { + version: String, + backtrace: snafu::Backtrace, + }, /// The given event was not found. - #[error("Not found error: {0}")] - NotFound(String), + #[snafu(display("Not found error: {message}"))] + NotFound { + message: String, + backtrace: snafu::Backtrace, + }, /// Invalid fields in the given PDU. - #[error("Invalid PDU: {0}")] - InvalidPdu(String), + #[snafu(display("Invalid PDU: {message}"))] + InvalidPdu { + message: String, + backtrace: snafu::Backtrace, + }, +} + +impl From for Error { + fn from(source: serde_json::Error) -> Self { SerdeJsonSnafu.into_error(source) } } diff --git a/src/core/matrix/state_res/event_auth.rs b/src/core/matrix/state_res/event_auth.rs index 24f03162..d9a6845e 100644 --- a/src/core/matrix/state_res/event_auth.rs +++ b/src/core/matrix/state_res/event_auth.rs @@ -24,6 +24,7 @@ use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue}; use super::{ Error, Event, Result, StateEventType, StateKey, TimelineEventType, + error::InvalidPduSnafu, power_levels::{ deserialize_power_levels, deserialize_power_levels_content_fields, deserialize_power_levels_content_invite, deserialize_power_levels_content_redact, @@ -383,8 +384,8 @@ where return Ok(false); } - let target_user = - <&UserId>::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{e}")))?; + let target_user = <&UserId>::try_from(state_key) + .map_err(|e| InvalidPduSnafu { message: format!("{e}") }.build())?; let user_for_join_auth = content .join_authorised_via_users_server @@ -461,7 +462,7 @@ where ?sender_membership_event_content, "Sender membership event content missing membership field" ); - return Err(Error::InvalidPdu("Missing membership field".to_owned())); + return Err(InvalidPduSnafu { message: "Missing membership field" }.build()); }; let membership_state = membership_state.deserialize()?; diff --git a/src/core/matrix/state_res/mod.rs b/src/core/matrix/state_res/mod.rs index 8370bc65..ef407cea 100644 --- a/src/core/matrix/state_res/mod.rs +++ b/src/core/matrix/state_res/mod.rs @@ -29,18 +29,18 @@ use ruma::{ }; use serde_json::from_str as from_json_str; -pub(crate) use self::error::Error; +pub(crate) use self::error::{Error, InvalidPduSnafu, NotFoundSnafu}; use self::power_levels::PowerLevelsContentFields; pub use self::{ event_auth::{auth_check, auth_types_for_event}, room_version::RoomVersion, }; +use super::{Event, StateKey}; use crate::{ - debug, debug_error, err, - matrix::{Event, StateKey}, + debug, debug_error, state_res::room_version::StateResolutionVersion, trace, - utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt}, + utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt}, warn, }; @@ -118,7 +118,10 @@ where let csg = calculate_conflicted_subgraph(&conflicting, event_fetch) .await .ok_or_else(|| { - Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned()) + InvalidPduSnafu { + message: "Failed to calculate conflicted subgraph", + } + .build() })?; debug!(count = csg.len(), "conflicted subgraph"); trace!(set = ?csg, "conflicted subgraph"); @@ -149,10 +152,11 @@ where let control_events: Vec<_> = all_conflicted .iter() .stream() - .wide_filter_map(async |id| { - is_power_event_id(id, &event_fetch) + .broad_filter_map(async |id| { + event_fetch(id.clone()) .await - .then_some(id.clone()) + .filter(|event| is_power_event(&event)) + .map(|_| id.clone()) }) .collect() .await; @@ -314,7 +318,10 @@ where trace!(event_id = event_id.as_str(), "fetching event for its auth events"); let evt = fetch_event(event_id.clone()).await; if evt.is_none() { - err!("could not fetch event {} to calculate conflicted subgraph", event_id); + tracing::error!( + "could not fetch event {} to calculate conflicted subgraph", + event_id + ); path.pop(); continue; } @@ -402,11 +409,11 @@ where let fetcher = async |event_id: OwnedEventId| { let pl = *event_to_pl .get(&event_id) - .ok_or_else(|| Error::NotFound(String::new()))?; + .ok_or_else(|| NotFoundSnafu { message: "" }.build())?; let ev = fetch_event(event_id) .await - .ok_or_else(|| Error::NotFound(String::new()))?; + .ok_or_else(|| NotFoundSnafu { message: "" }.build())?; Ok((pl, ev.origin_server_ts())) }; @@ -612,9 +619,12 @@ where let events_to_check: Vec<_> = events_to_check .map(Result::Ok) .broad_and_then(async |event_id| { - fetch_event(event_id.to_owned()) - .await - .ok_or_else(|| Error::NotFound(format!("Failed to find {event_id}"))) + fetch_event(event_id.to_owned()).await.ok_or_else(|| { + NotFoundSnafu { + message: format!("Failed to find {event_id}"), + } + .build() + }) }) .try_collect() .boxed() @@ -653,7 +663,7 @@ where trace!(event_id = event.event_id().as_str(), "checking event"); let state_key = event .state_key() - .ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; + .ok_or_else(|| InvalidPduSnafu { message: "State event had no state key" }.build())?; let auth_types = auth_types_for_event( event.event_type(), @@ -669,13 +679,14 @@ where trace!("room version uses hashed IDs, manually fetching create event"); let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$"); let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| { - Error::InvalidPdu(format!( - "Failed to parse create event ID from room ID/hash: {e}" - )) + InvalidPduSnafu { + message: format!("Failed to parse create event ID from room ID/hash: {e}"), + } + .build() + })?; + let create_event = fetch_event(create_event_id.into()).await.ok_or_else(|| { + NotFoundSnafu { message: "Failed to find create event" }.build() })?; - let create_event = fetch_event(create_event_id.into()) - .await - .ok_or_else(|| Error::NotFound("Failed to find create event".into()))?; auth_state.insert(create_event.event_type().with_state_key(""), create_event); } for aid in event.auth_events() { @@ -686,7 +697,7 @@ where auth_state.insert( ev.event_type() .with_state_key(ev.state_key().ok_or_else(|| { - Error::InvalidPdu("State event had no state key".to_owned()) + InvalidPduSnafu { message: "State event had no state key" }.build() })?), ev.clone(), ); @@ -801,13 +812,13 @@ where let event = fetch_event(p.clone()) .await - .ok_or_else(|| Error::NotFound(format!("Failed to find {p}")))?; + .ok_or_else(|| NotFoundSnafu { message: format!("Failed to find {p}") }.build())?; pl = None; for aid in event.auth_events() { - let ev = fetch_event(aid.to_owned()) - .await - .ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?; + let ev = fetch_event(aid.to_owned()).await.ok_or_else(|| { + NotFoundSnafu { message: format!("Failed to find {aid}") }.build() + })?; if is_type_and_key(&ev, &TimelineEventType::RoomPowerLevels, "") { pl = Some(aid.to_owned()); @@ -869,9 +880,9 @@ where event = None; for aid in sort_ev.auth_events() { - let aev = fetch_event(aid.to_owned()) - .await - .ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?; + let aev = fetch_event(aid.to_owned()).await.ok_or_else(|| { + NotFoundSnafu { message: format!("Failed to find {aid}") }.build() + })?; if is_type_and_key(&aev, &TimelineEventType::RoomPowerLevels, "") { event = Some(aev); @@ -915,6 +926,7 @@ async fn add_event_and_auth_chain_to_graph( } } +#[allow(dead_code)] async fn is_power_event_id(event_id: &EventId, fetch: &F) -> bool where F: Fn(OwnedEventId) -> Fut + Sync, diff --git a/src/core/matrix/state_res/room_version.rs b/src/core/matrix/state_res/room_version.rs index 87af9f94..3d4aefec 100644 --- a/src/core/matrix/state_res/room_version.rs +++ b/src/core/matrix/state_res/room_version.rs @@ -1,6 +1,6 @@ use ruma::RoomVersionId; -use super::{Error, Result}; +use super::{Result, error::UnsupportedSnafu}; #[derive(Debug)] #[allow(clippy::exhaustive_enums)] @@ -163,7 +163,11 @@ impl RoomVersion { | RoomVersionId::V10 => Self::V10, | RoomVersionId::V11 => Self::V11, | RoomVersionId::V12 => Self::V12, - | ver => return Err(Error::Unsupported(format!("found version `{ver}`"))), + | ver => + return Err(UnsupportedSnafu { + version: format!("found version `{ver}`"), + } + .build()), }) } } diff --git a/src/core/matrix/state_res/test_utils.rs b/src/core/matrix/state_res/test_utils.rs index 10b79de1..88234813 100644 --- a/src/core/matrix/state_res/test_utils.rs +++ b/src/core/matrix/state_res/test_utils.rs @@ -22,7 +22,7 @@ use serde_json::{ value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value}, }; -use super::auth_types_for_event; +use super::{auth_types_for_event, error::NotFoundSnafu}; use crate::{ Result, RoomVersion, info, matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash}, @@ -232,7 +232,7 @@ impl TestStore { self.0 .get(event_id) .cloned() - .ok_or_else(|| super::Error::NotFound(format!("{event_id} not found"))) + .ok_or_else(|| NotFoundSnafu { message: format!("{event_id} not found") }.build()) .map_err(Into::into) } diff --git a/src/core/mod.rs b/src/core/mod.rs index bcd6af83..e9a2e24f 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -14,9 +14,11 @@ pub mod utils; pub use ::arrayvec; pub use ::http; +pub use ::paste; pub use ::ruma; pub use ::smallstr; pub use ::smallvec; +pub use ::snafu; pub use ::toml; pub use ::tracing; pub use config::Config; diff --git a/src/core/utils/sys/compute.rs b/src/core/utils/sys/compute.rs index 5274cd66..84669f45 100644 --- a/src/core/utils/sys/compute.rs +++ b/src/core/utils/sys/compute.rs @@ -2,6 +2,8 @@ use std::{cell::Cell, fmt::Debug, path::PathBuf, sync::LazyLock}; +use snafu::IntoError; + use crate::{Result, is_equal_to}; type Id = usize; @@ -142,7 +144,9 @@ pub fn getcpu() -> Result { #[cfg(not(target_os = "linux"))] #[inline] -pub fn getcpu() -> Result { Err(crate::Error::Io(std::io::ErrorKind::Unsupported.into())) } +pub fn getcpu() -> Result { + Err(crate::error::IoSnafu.into_error(std::io::ErrorKind::Unsupported.into())) +} fn query_cores_available() -> impl Iterator { core_affinity::get_core_ids() diff --git a/src/database/de.rs b/src/database/de.rs index 21b9311e..c1d863d4 100644 --- a/src/database/de.rs +++ b/src/database/de.rs @@ -255,7 +255,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { | "$serde_json::private::RawValue" => visitor.visit_map(self), | "Cbor" => visitor .visit_newtype_struct(&mut minicbor_serde::Deserializer::new(self.record_trail())) - .map_err(|e| Self::Error::SerdeDe(e.to_string().into())), + .map_err(|e| { + let message: std::borrow::Cow<'static, str> = e.to_string().into(); + conduwuit_core::error::SerdeDeSnafu { message }.build() + }), | _ => visitor.visit_newtype_struct(self), } @@ -313,9 +316,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { let end = self.pos.saturating_add(BYTES).min(self.buf.len()); let bytes: ArrayVec = self.buf[self.pos..end].try_into()?; - let bytes = bytes - .into_inner() - .map_err(|_| Self::Error::SerdeDe("i64 buffer underflow".into()))?; + let bytes = bytes.into_inner().map_err(|_| { + let message: std::borrow::Cow<'static, str> = "i64 buffer underflow".into(); + conduwuit_core::error::SerdeDeSnafu { message }.build() + })?; self.inc_pos(BYTES); visitor.visit_i64(i64::from_be_bytes(bytes)) @@ -345,9 +349,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { let end = self.pos.saturating_add(BYTES).min(self.buf.len()); let bytes: ArrayVec = self.buf[self.pos..end].try_into()?; - let bytes = bytes - .into_inner() - .map_err(|_| Self::Error::SerdeDe("u64 buffer underflow".into()))?; + let bytes = bytes.into_inner().map_err(|_| { + let message: std::borrow::Cow<'static, str> = "u64 buffer underflow".into(); + conduwuit_core::error::SerdeDeSnafu { message }.build() + })?; self.inc_pos(BYTES); visitor.visit_u64(u64::from_be_bytes(bytes)) diff --git a/src/database/ser.rs b/src/database/ser.rs index 2e1a2cb0..f7911014 100644 --- a/src/database/ser.rs +++ b/src/database/ser.rs @@ -199,7 +199,10 @@ impl ser::Serializer for &mut Serializer<'_, W> { value .serialize(&mut Serializer::new(&mut Writer::new(&mut self.out))) - .map_err(|e| Self::Error::SerdeSer(e.to_string().into())) + .map_err(|e| { + let message: std::borrow::Cow<'static, str> = e.to_string().into(); + conduwuit_core::error::SerdeSerSnafu { message }.build() + }) }, | _ => unhandled!("Unrecognized serialization Newtype {name:?}"), } diff --git a/src/router/router.rs b/src/router/router.rs index fdaf9126..c2bc8312 100644 --- a/src/router/router.rs +++ b/src/router/router.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; use axum::{Router, response::IntoResponse}; use conduwuit::Error; @@ -18,5 +18,10 @@ pub(crate) fn build(services: &Arc) -> (Router, Guard) { } async fn not_found(_uri: Uri) -> impl IntoResponse { - Error::Request(ErrorKind::Unrecognized, "Not Found".into(), StatusCode::NOT_FOUND) + Error::Request { + kind: ErrorKind::Unrecognized, + message: Cow::Borrowed("Not Found"), + code: StatusCode::NOT_FOUND, + backtrace: None, + } } diff --git a/src/service/appservice/mod.rs b/src/service/appservice/mod.rs index 2e992f28..324c9c57 100644 --- a/src/service/appservice/mod.rs +++ b/src/service/appservice/mod.rs @@ -147,11 +147,11 @@ impl Service { // same appservice) if let Ok(existing) = self.find_from_token(®istration.as_token).await { if existing.registration.id != registration.id { - return Err(err!(Request(InvalidParam( + return Err!(Request(InvalidParam( "Cannot register appservice: Token is already used by appservice '{}'. \ Please generate a different token.", existing.registration.id - )))); + ))); } } @@ -163,10 +163,10 @@ impl Service { .await .is_ok() { - return Err(err!(Request(InvalidParam( + return Err!(Request(InvalidParam( "Cannot register appservice: The provided token is already in use by a user \ device. Please generate a different token for the appservice." - )))); + ))); } self.db diff --git a/src/service/federation/execute.rs b/src/service/federation/execute.rs index 2f635503..55b2acda 100644 --- a/src/service/federation/execute.rs +++ b/src/service/federation/execute.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, mem}; use bytes::Bytes; use conduwuit::{ - Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err, + Err, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err, error::inspect_debug_log, implement, trace, }; use http::{HeaderValue, header::AUTHORIZATION}; @@ -179,10 +179,7 @@ async fn into_http_response( debug!("Got {status:?} for {method} {url}"); if !status.is_success() { - return Err(Error::Federation( - dest.to_owned(), - RumaError::from_http_response(http_response), - )); + return Err!(Federation(dest.to_owned(), RumaError::from_http_response(http_response),)); } Ok(http_response) diff --git a/src/service/media/remote.rs b/src/service/media/remote.rs index da457a8b..824d7791 100644 --- a/src/service/media/remote.rs +++ b/src/service/media/remote.rs @@ -35,7 +35,7 @@ pub async fn fetch_remote_thumbnail( .fetch_thumbnail_authenticated(mxc, user, server, timeout_ms, dim) .await; - if let Err(Error::Request(NotFound, ..)) = &result { + if let Err(Error::Request { kind: NotFound, .. }) = &result { return self .fetch_thumbnail_unauthenticated(mxc, user, server, timeout_ms, dim) .await; @@ -67,7 +67,7 @@ pub async fn fetch_remote_content( ); }); - if let Err(Error::Request(Unrecognized, ..)) = &result { + if let Err(Error::Request { kind: Unrecognized, .. }) = &result { return self .fetch_content_unauthenticated(mxc, user, server, timeout_ms) .await; diff --git a/src/service/rooms/event_handler/resolve_state.rs b/src/service/rooms/event_handler/resolve_state.rs index cd747e04..2ca8700a 100644 --- a/src/service/rooms/event_handler/resolve_state.rs +++ b/src/service/rooms/event_handler/resolve_state.rs @@ -112,7 +112,14 @@ where { let event_fetch = |event_id| self.event_fetch(event_id); let event_exists = |event_id| self.event_exists(event_id); - state_res::resolve(room_version, state_sets, auth_chain_sets, &event_fetch, &event_exists) - .map_err(|e| err!(error!("State resolution failed: {e:?}"))) - .await + Ok( + state_res::resolve( + room_version, + state_sets, + auth_chain_sets, + &event_fetch, + &event_exists, + ) + .await?, + ) } diff --git a/src/service/rooms/spaces/pagination_token.rs b/src/service/rooms/spaces/pagination_token.rs index d97b7a2f..211cd7b4 100644 --- a/src/service/rooms/spaces/pagination_token.rs +++ b/src/service/rooms/spaces/pagination_token.rs @@ -3,7 +3,7 @@ use std::{ str::FromStr, }; -use conduwuit::{Error, Result}; +use conduwuit::{Err, Error, Result}; use ruma::{UInt, api::client::error::ErrorKind}; use crate::rooms::short::ShortRoomId; @@ -57,7 +57,7 @@ impl FromStr for PaginationToken { if let Some(token) = pag_tok() { Ok(token) } else { - Err(Error::BadRequest(ErrorKind::InvalidParam, "invalid token")) + Err!(BadRequest(ErrorKind::InvalidParam, "invalid token")) } } } diff --git a/src/service/rooms/timeline/create.rs b/src/service/rooms/timeline/create.rs index 40e41b08..8daa7944 100644 --- a/src/service/rooms/timeline/create.rs +++ b/src/service/rooms/timeline/create.rs @@ -75,10 +75,7 @@ pub async fn create_hash_and_sign_event( let content: RoomCreateEventContent = serde_json::from_str(content.get())?; Ok(content.room_version) } else { - Err(Error::InconsistentRoomState( - "non-create event for room of unknown version", - room_id, - )) + Err!(InconsistentRoomState("non-create event for room of unknown version", room_id)) } } let PduBuilder { @@ -275,7 +272,9 @@ pub async fn create_hash_and_sign_event( .hash_and_sign_event(&mut pdu_json, &room_version_id) { return match e { - | Error::Signatures(ruma::signatures::Error::PduSize) => { + | Error::Signatures { source, .. } + if matches!(source, ruma::signatures::Error::PduSize) => + { Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)"))) }, | _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))), diff --git a/src/service/uiaa/mod.rs b/src/service/uiaa/mod.rs index 7c15a919..adc589a4 100644 --- a/src/service/uiaa/mod.rs +++ b/src/service/uiaa/mod.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, sync::Arc}; use conduwuit::{ - Err, Error, Result, SyncRwLock, err, error, implement, utils, + Err, Result, SyncRwLock, err, error, implement, utils, utils::{hash, string::EMPTY}, }; use database::{Deserialized, Json, Map}; @@ -117,7 +117,7 @@ pub async fn try_auth( } else if let Some(username) = user { username } else { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::Unrecognized, "Identifier type not recognized.", )); @@ -125,7 +125,7 @@ pub async fn try_auth( #[cfg(not(feature = "element_hacks"))] let Some(UserIdentifier::UserIdOrLocalpart(username)) = identifier else { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::Unrecognized, "Identifier type not recognized.", )); @@ -135,7 +135,7 @@ pub async fn try_auth( username.clone(), self.services.globals.server_name(), ) - .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "User ID is invalid."))?; + .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "User ID is invalid.")))?; // Check if the access token being used matches the credentials used for UIAA if user_id.localpart() != user_id_from_username.localpart() { diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index 1b96203c..dd45e292 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -761,13 +761,13 @@ impl Service { .keys .into_values(); - let self_signing_key_id = self_signing_key_ids.next().ok_or(Error::BadRequest( + let self_signing_key_id = self_signing_key_ids.next().ok_or(err!(BadRequest( ErrorKind::InvalidParam, "Self signing key contained no key.", - ))?; + )))?; if self_signing_key_ids.next().is_some() { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::InvalidParam, "Self signing key contained more than one key.", )); @@ -1439,13 +1439,13 @@ pub fn parse_master_key( let master_key = master_key .deserialize() - .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid master key"))?; + .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid master key")))?; let mut master_key_ids = master_key.keys.values(); let master_key_id = master_key_ids .next() - .ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Master key contained no key."))?; + .ok_or(err!(BadRequest(ErrorKind::InvalidParam, "Master key contained no key.")))?; if master_key_ids.next().is_some() { - return Err(Error::BadRequest( + return Err!(BadRequest( ErrorKind::InvalidParam, "Master key contained more than one key.", )); diff --git a/src/web/Cargo.toml b/src/web/Cargo.toml index 84de46da..cfc9d27a 100644 --- a/src/web/Cargo.toml +++ b/src/web/Cargo.toml @@ -25,7 +25,7 @@ axum.workspace = true futures.workspace = true tracing.workspace = true rand.workspace = true -thiserror.workspace = true +snafu.workspace = true [lints] workspace = true diff --git a/src/web/mod.rs b/src/web/mod.rs index 25da1139..865d2e24 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -8,6 +8,7 @@ use axum::{ }; use conduwuit_build_metadata::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL, version_tag}; use conduwuit_service::state; +use snafu::{IntoError, prelude::*}; pub fn build() -> Router { Router::::new() @@ -48,10 +49,17 @@ async fn logo_handler() -> impl IntoResponse { ) } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Snafu)] enum WebError { - #[error("Failed to render template: {0}")] - Render(#[from] askama::Error), + #[snafu(display("Failed to render template: {source}"))] + Render { + source: askama::Error, + backtrace: snafu::Backtrace, + }, +} + +impl From for WebError { + fn from(source: askama::Error) -> Self { RenderSnafu.into_error(source) } } impl IntoResponse for WebError { @@ -66,7 +74,7 @@ impl IntoResponse for WebError { let nonce = rand::random::().to_string(); let status = match &self { - | Self::Render(_) => StatusCode::INTERNAL_SERVER_ERROR, + | Self::Render { .. } => StatusCode::INTERNAL_SERVER_ERROR, }; let tmpl = Error { nonce: &nonce, err: self }; if let Ok(body) = tmpl.render() { diff --git a/src/web/templates/error.html.j2 b/src/web/templates/error.html.j2 index e320d0ed..90c76671 100644 --- a/src/web/templates/error.html.j2 +++ b/src/web/templates/error.html.j2 @@ -12,8 +12,8 @@ Server Error {%- match err -%} - {% when WebError::Render(err) -%} -
{{ err }}
+ {% when WebError::Render { source, .. } -%} +
{{ source }}
{% else -%}

An error occurred

{%- endmatch -%}