Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Jade Ellis
7a8c409ff9
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.
2026-02-22 14:06:15 +00:00
51 changed files with 907 additions and 391 deletions

26
Cargo.lock generated
View file

@ -1014,6 +1014,7 @@ dependencies = [
"nix", "nix",
"num-traits", "num-traits",
"parking_lot", "parking_lot",
"paste",
"rand 0.10.0", "rand 0.10.0",
"rand_core 0.6.4", "rand_core 0.6.4",
"regex", "regex",
@ -1027,7 +1028,7 @@ dependencies = [
"serde_regex", "serde_regex",
"smallstr", "smallstr",
"smallvec", "smallvec",
"thiserror 2.0.18", "snafu",
"tikv-jemalloc-ctl", "tikv-jemalloc-ctl",
"tikv-jemalloc-sys", "tikv-jemalloc-sys",
"tikv-jemallocator", "tikv-jemallocator",
@ -1154,7 +1155,7 @@ dependencies = [
"conduwuit_service", "conduwuit_service",
"futures", "futures",
"rand 0.10.0", "rand 0.10.0",
"thiserror 2.0.18", "snafu",
"tracing", "tracing",
] ]
@ -4911,6 +4912,27 @@ dependencies = [
"serde", "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]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.10" version = "0.5.10"

View file

@ -307,9 +307,14 @@ features = [
] ]
# Used for conduwuit::Error type # Used for conduwuit::Error type
[workspace.dependencies.thiserror] [workspace.dependencies.snafu]
version = "2.0.12" version = "0.8"
default-features = false default-features = false
features = ["std", "rust_1_81"]
# Used for macro name generation
[workspace.dependencies.paste]
version = "1.0"
# Used when hashing the state # Used when hashing the state
[workspace.dependencies.ring] [workspace.dependencies.ring]

View file

@ -3,7 +3,7 @@ use std::fmt::Write;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Event, Result, debug_info, err, error, info, Err, Event, Result, debug_info, err, error, info,
matrix::pdu::PduBuilder, matrix::pdu::PduBuilder,
utils::{self, ReadyExt, stream::BroadbandExt}, utils::{self, ReadyExt, stream::BroadbandExt},
warn, warn,
@ -387,7 +387,7 @@ pub(crate) async fn register_route(
) )
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@ -401,7 +401,7 @@ pub(crate) async fn register_route(
&uiaainfo, &uiaainfo,
json, json,
); );
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("JSON body is not valid"))); return Err!(Request(NotJson("JSON body is not valid")));
@ -661,7 +661,7 @@ pub(crate) async fn change_password_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
@ -673,7 +673,7 @@ pub(crate) async fn change_password_route(
.uiaa .uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json); .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"))); return Err!(Request(NotJson("JSON body is not valid")));
@ -791,7 +791,7 @@ pub(crate) async fn deactivate_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@ -802,7 +802,7 @@ pub(crate) async fn deactivate_route(
.uiaa .uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json); .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"))); return Err!(Request(NotJson("JSON body is not valid")));

View file

@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Error, Result, debug, err, utils}; use conduwuit::{Err, Result, debug, err, utils};
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
MilliSecondsSinceUnixEpoch, OwnedDeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
@ -232,7 +232,7 @@ pub(crate) async fn delete_devices_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@ -243,10 +243,10 @@ pub(crate) async fn delete_devices_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .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."));
}, },
}, },
} }

View file

@ -5,7 +5,7 @@ use std::{
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, debug_warn, err, Err, Result, debug, debug_warn, err,
result::NotFound, result::NotFound,
utils, utils,
utils::{IterStream, stream::WidebandExt}, utils::{IterStream, stream::WidebandExt},
@ -215,7 +215,7 @@ pub(crate) async fn upload_signing_keys_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@ -226,10 +226,10 @@ pub(crate) async fn upload_signing_keys_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .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 let from = body
.from .from
.parse() .parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `from`.")))?;
let to = body let to = body
.to .to
.parse() .parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `to`.")))?;
device_list_updates.extend( device_list_updates.extend(
services services

View file

@ -3,7 +3,7 @@ use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Result, err, Err, Result, err, error,
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize}, utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
}; };
use conduwuit_service::{ 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) .create(mxc, Some(user), Some(&content_disposition), content_type, &body.file)
.await .await
{ {
err!("Failed to save uploaded media: {e}"); error!("Failed to save uploaded media: {e}");
return Err!(Request(Unknown("Failed to save uploaded media"))); return Err!(Request(Unknown("Failed to save uploaded media")));
} }

View file

@ -1,7 +1,7 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Result, at, debug_warn, Err, Result, at, debug_warn,
matrix::{ matrix::{
event::{Event, Matches}, event::{Event, Matches},
pdu::PduCount, pdu::PduCount,
@ -322,7 +322,7 @@ where
if server_ignored { if server_ignored {
// the sender's server is ignored, so ignore this event // the sender's server is ignored, so ignore this event
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::SenderIgnored { sender: None }, ErrorKind::SenderIgnored { sender: None },
"The sender's server is ignored by this server.", "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 { 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 // the recipient of this PDU has the sender ignored, and we're not
// configured to send ignored messages to clients // configured to send ignored messages to clients
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) }, ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) },
"You have ignored this sender.", "You have ignored this sender.",
)); ));

View file

@ -1,5 +1,5 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, err}; use conduwuit::{Err, Result, err};
use conduwuit_service::Services; use conduwuit_service::Services;
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, CanonicalJsonObject, CanonicalJsonValue,
@ -243,27 +243,27 @@ pub(crate) async fn set_pushrule_route(
body.before.as_deref(), body.before.as_deref(),
) { ) {
let err = match error { let err = match error {
| InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest( | InsertPushRuleError::ServerDefaultRuleId => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Rule IDs starting with a dot are reserved for server-default rules.", "Rule IDs starting with a dot are reserved for server-default rules.",
), )),
| InsertPushRuleError::InvalidRuleId => Error::BadRequest( | InsertPushRuleError::InvalidRuleId => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Rule ID containing invalid characters.", "Rule ID containing invalid characters.",
), )),
| InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest( | InsertPushRuleError::RelativeToServerDefaultRule => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Can't place a push rule relatively to a server-default rule.", "Can't place a push rule relatively to a server-default rule.",
), )),
| InsertPushRuleError::UnknownRuleId => Error::BadRequest( | InsertPushRuleError::UnknownRuleId => err!(BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"The before or after rule could not be found.", "The before or after rule could not be found.",
), )),
| InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest( | InsertPushRuleError::BeforeHigherThanAfter => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"The before rule has a higher priority than the after rule.", "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); return Err(err);
@ -433,13 +433,13 @@ pub(crate) async fn delete_pushrule_route(
.remove(body.kind.clone(), &body.rule_id) .remove(body.kind.clone(), &body.rule_id)
{ {
let err = match error { let err = match error {
| RemovePushRuleError::ServerDefault => Error::BadRequest( | RemovePushRuleError::ServerDefault => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Cannot delete a server-default pushrule.", "Cannot delete a server-default pushrule.",
), )),
| RemovePushRuleError::NotFound => | RemovePushRuleError::NotFound =>
Error::BadRequest(ErrorKind::NotFound, "Push rule not found."), err!(BadRequest(ErrorKind::NotFound, "Push rule not found.")),
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."), | _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")),
}; };
return Err(err); return Err(err);

View file

@ -2,7 +2,7 @@ use std::cmp::max;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Event, Result, RoomVersion, debug, err, info, Err, Event, Result, RoomVersion, debug, err, info,
matrix::{StateKey, pdu::PduBuilder}, matrix::{StateKey, pdu::PduBuilder},
}; };
use futures::{FutureExt, StreamExt}; 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"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services.server.supported_room_version(&body.new_version) { if !services.server.supported_room_version(&body.new_version) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::UnsupportedRoomVersion, ErrorKind::UnsupportedRoomVersion,
"This server does not support that room version.", "This server does not support that room version.",
)); ));
@ -170,7 +170,7 @@ pub(crate) async fn upgrade_room_route(
"creator".into(), "creator".into(),
json!(&sender_user).try_into().map_err(|e| { json!(&sender_user).try_into().map_err(|e| {
info!("Error forming creation event: {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(), "room_version".into(),
json!(&body.new_version) json!(&body.new_version)
.try_into() .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( create_event_content.insert(
"predecessor".into(), "predecessor".into(),
json!(predecessor) json!(predecessor)
.try_into() .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 // Validate creation event content
@ -203,7 +203,7 @@ pub(crate) async fn upgrade_room_route(
) )
.is_err() .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 let create_event_id = services

View file

@ -3,7 +3,7 @@ use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, err, info, Err, Result, debug, err, info,
utils::{self, ReadyExt, hash}, utils::{self, ReadyExt, hash},
warn, warn,
}; };
@ -191,7 +191,7 @@ pub(crate) async fn handle_login(
} }
if services.users.is_locked(&user_id).await? { 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 { if services.users.is_login_disabled(&user_id).await {
@ -390,7 +390,7 @@ pub(crate) async fn login_token_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
@ -402,7 +402,7 @@ pub(crate) async fn login_token_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .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."))); return Err!(Request(NotJson("No JSON body was sent when required.")));

View file

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Result, err};
use conduwuit_service::sending::EduBuf; use conduwuit_service::sending::EduBuf;
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
@ -66,7 +66,7 @@ pub(crate) async fn send_event_to_device_route(
let event = event let event = event
.deserialize_as() .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 { match target_device_id_maybe {
| DeviceIdOrAllDevices::DeviceId(target_device_id) => { | DeviceIdOrAllDevices::DeviceId(target_device_id) => {

View file

@ -1,11 +1,8 @@
use axum::{Json, extract::State, response::IntoResponse}; use axum::{Json, extract::State, response::IntoResponse};
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use ruma::api::client::{ use ruma::api::client::discovery::{
discovery::{ discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo}, discover_support::{self, Contact},
discover_support::{self, Contact},
},
error::ErrorKind,
}; };
use crate::Ruma; use crate::Ruma;
@ -19,7 +16,7 @@ pub(crate) async fn well_known_client(
) -> Result<discover_homeserver::Response> { ) -> Result<discover_homeserver::Response> {
let client_url = match services.config.well_known.client.as_ref() { let client_url = match services.config.well_known.client.as_ref() {
| Some(url) => url.to_string(), | 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 { Ok(discover_homeserver::Response {
@ -88,7 +85,7 @@ pub(crate) async fn well_known_support(
if contacts.is_empty() && support_page.is_none() { if contacts.is_empty() && support_page.is_none() {
// No admin room, no configured contacts, and no support page // 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 }) Ok(discover_support::Response { contacts, support_page })
@ -105,7 +102,7 @@ pub(crate) async fn syncv3_client_server_json(
| Some(url) => url.to_string(), | Some(url) => url.to_string(),
| None => match services.config.well_known.server.as_ref() { | None => match services.config.well_known.server.as_ref() {
| Some(url) => url.to_string(), | Some(url) => url.to_string(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
}, },
}; };

View file

@ -4,7 +4,7 @@ use axum_extra::{
headers::{Authorization, authorization::Bearer}, headers::{Authorization, authorization::Bearer},
typed_header::TypedHeaderRejectionReason, typed_header::TypedHeaderRejectionReason,
}; };
use conduwuit::{Err, Error, Result, debug_error, err, warn}; use conduwuit::{Err, Result, debug_error, err, warn};
use futures::{ use futures::{
TryFutureExt, TryFutureExt,
future::{ future::{
@ -77,7 +77,7 @@ pub(super) async fn auth(
// already // already
}, },
| Token::None | Token::Invalid => { | Token::None | Token::Invalid => {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::MissingToken, ErrorKind::MissingToken,
"Missing or invalid access token.", "Missing or invalid access token.",
)); ));
@ -96,7 +96,7 @@ pub(super) async fn auth(
// already // already
}, },
| Token::None | Token::Invalid => { | Token::None | Token::Invalid => {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::MissingToken, ErrorKind::MissingToken,
"Missing or invalid access token.", "Missing or invalid access token.",
)); ));
@ -130,10 +130,10 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}) })
} else { } 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, 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::v3::Request::METADATA
| &ruma::api::client::session::logout_all::v3::Request::METADATA | &ruma::api::client::session::logout_all::v3::Request::METADATA
) { ) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::UserLocked, ErrorKind::UserLocked,
"This account has been locked.", "This account has been locked.",
)); ));
@ -174,11 +174,11 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}), }),
| (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) => | (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) =>
Err(Error::BadRequest( Err!(BadRequest(
ErrorKind::Unauthorized, ErrorKind::Unauthorized,
"Only server signatures should be used on this endpoint.", "Only server signatures should be used on this endpoint.",
)), )),
| (AuthScheme::AppserviceToken, Token::User(_)) => Err(Error::BadRequest( | (AuthScheme::AppserviceToken, Token::User(_)) => Err!(BadRequest(
ErrorKind::Unauthorized, ErrorKind::Unauthorized,
"Only appservice access tokens should be used on this endpoint.", "Only appservice access tokens should be used on this endpoint.",
)), )),
@ -196,13 +196,13 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}) })
} else { } else {
Err(Error::BadRequest( Err!(BadRequest(
ErrorKind::UnknownToken { soft_logout: false }, ErrorKind::UnknownToken { soft_logout: false },
"Unknown access token.", "Unknown access token.",
)) ))
} }
}, },
| (_, Token::Invalid) => Err(Error::BadRequest( | (_, Token::Invalid) => Err!(BadRequest(
ErrorKind::UnknownToken { soft_logout: false }, ErrorKind::UnknownToken { soft_logout: false },
"Unknown access token.", "Unknown access token.",
)), )),

View file

@ -1,12 +1,9 @@
use std::{borrow::Borrow, iter::once}; use std::{borrow::Borrow, iter::once};
use axum::extract::State; 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 futures::StreamExt;
use ruma::{ use ruma::{RoomId, api::federation::authorization::get_event_authorization};
RoomId,
api::{client::error::ErrorKind, federation::authorization::get_event_authorization},
};
use super::AccessCheck; use super::AccessCheck;
use crate::Ruma; use crate::Ruma;
@ -47,7 +44,7 @@ pub(crate) async fn get_event_authorization_route(
.timeline .timeline
.get_pdu_json(&body.event_id) .get_pdu_json(&body.event_id)
.await .await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "Event not found."))?; .map_err(|_| err!(BadRequest(ErrorKind::NotFound, "Event not found.")))?;
let room_id_str = event let room_id_str = event
.get("room_id") .get("room_id")

View file

@ -2,7 +2,7 @@ use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use base64::{Engine as _, engine::general_purpose}; use base64::{Engine as _, engine::general_purpose};
use conduwuit::{ use conduwuit::{
Err, Error, PduEvent, Result, err, error, Err, PduEvent, Result, err, error,
matrix::{Event, event::gen_event_id}, matrix::{Event, event::gen_event_id},
utils::{self, hash::sha256}, utils::{self, hash::sha256},
warn, warn,
@ -33,7 +33,7 @@ pub(crate) async fn create_invite_route(
.await?; .await?;
if !services.server.supported_room_version(&body.room_version) { if !services.server.supported_room_version(&body.room_version) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() }, ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() },
"Server does not support this room version.", "Server does not support this room version.",
)); ));

View file

@ -1,7 +1,7 @@
use std::borrow::ToOwned; use std::borrow::ToOwned;
use axum::extract::State; 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 conduwuit_service::Services;
use futures::StreamExt; use futures::StreamExt;
use ruma::{ 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?; let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
if !body.ver.contains(&room_version_id) { if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Room version not supported.", "Room version not supported.",
)); ));

View file

@ -1,6 +1,6 @@
use RoomVersionId::*; use RoomVersionId::*;
use axum::extract::State; 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::{ use ruma::{
RoomVersionId, RoomVersionId,
api::{client::error::ErrorKind, federation::knock::create_knock_event_template}, 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?; 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) { 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 }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Room version does not support knocking.", "Room version does not support knocking.",
)); ));
} }
if !body.ver.contains(&room_version_id) { if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Your homeserver does not support the features required to knock on this room.", "Your homeserver does not support the features required to knock on this room.",
)); ));

View file

@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result, err};
use ruma::{ use ruma::{
api::{ api::{
client::error::ErrorKind, client::error::ErrorKind,
@ -25,7 +25,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
.config .config
.allow_public_room_directory_over_federation .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( let response = crate::client::get_public_rooms_filtered_helper(
@ -38,7 +38,10 @@ pub(crate) async fn get_public_rooms_filtered_route(
) )
.await .await
.map_err(|_| { .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 { Ok(get_public_rooms_filtered::v1::Response {
@ -62,7 +65,7 @@ pub(crate) async fn get_public_rooms_route(
.globals .globals
.allow_public_room_directory_over_federation() .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( let response = crate::client::get_public_rooms_filtered_helper(
@ -75,7 +78,10 @@ pub(crate) async fn get_public_rooms_route(
) )
.await .await
.map_err(|_| { .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 { Ok(get_public_rooms::v1::Response {

View file

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result, err}; use conduwuit::{Err, Result, err};
use futures::StreamExt; use futures::StreamExt;
use get_profile_information::v1::ProfileField; use get_profile_information::v1::ProfileField;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
@ -67,17 +67,16 @@ pub(crate) async fn get_profile_information_route(
.config .config
.allow_inbound_profile_lookup_federation_requests .allow_inbound_profile_lookup_federation_requests
{ {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"Profile lookup over federation is not allowed on this homeserver.", "Profile lookup over federation is not allowed on this homeserver.",
)); ));
} }
if !services.globals.server_is_ours(body.user_id.server_name()) { if !services.globals.server_is_ours(body.user_id.server_name()) {
return Err(Error::BadRequest( return Err!(
ErrorKind::InvalidParam, BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",)
"User does not belong to this server.", );
));
} }
let mut displayname = None; let mut displayname = None;

View file

@ -114,7 +114,7 @@ pub(crate) async fn send_transaction_message_route(
); );
for (id, result) in &results { for (id, result) in &results {
if let Err(e) = result { 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:?}"); warn!("Incoming PDU failed {id}: {e:?}");
} }
} }

View file

@ -1,7 +1,7 @@
use std::time::Duration; use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use futures::{FutureExt, StreamExt, TryFutureExt}; use futures::{FutureExt, StreamExt, TryFutureExt};
use ruma::api::{ use ruma::api::{
client::error::ErrorKind, client::error::ErrorKind,
@ -24,7 +24,7 @@ pub(crate) async fn get_devices_route(
body: Ruma<get_devices::v1::Request>, body: Ruma<get_devices::v1::Request>,
) -> Result<get_devices::v1::Response> { ) -> Result<get_devices::v1::Response> {
if !services.globals.user_is_local(&body.user_id) { if !services.globals.user_is_local(&body.user_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Tried to access user from other server.", "Tried to access user from other server.",
)); ));
@ -86,10 +86,9 @@ pub(crate) async fn get_keys_route(
.iter() .iter()
.any(|(u, _)| !services.globals.user_is_local(u)) .any(|(u, _)| !services.globals.user_is_local(u))
{ {
return Err(Error::BadRequest( return Err!(
ErrorKind::InvalidParam, BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",)
"User does not belong to this server.", );
));
} }
let result = get_keys_helper( let result = get_keys_helper(
@ -121,7 +120,7 @@ pub(crate) async fn claim_keys_route(
.iter() .iter()
.any(|(u, _)| !services.globals.user_is_local(u)) .any(|(u, _)| !services.globals.user_is_local(u))
{ {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Tried to access user from other server.", "Tried to access user from other server.",
)); ));

View file

@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use ruma::api::{client::error::ErrorKind, federation::discovery::discover_homeserver}; use ruma::api::federation::discovery::discover_homeserver;
use crate::Ruma; use crate::Ruma;
@ -14,7 +14,7 @@ pub(crate) async fn well_known_server(
Ok(discover_homeserver::Response { Ok(discover_homeserver::Response {
server: match services.server.config.well_known.server.as_ref() { server: match services.server.config.well_known.server.as_ref() {
| Some(server_name) => server_name.to_owned(), | Some(server_name) => server_name.to_owned(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
}, },
}) })
} }

View file

@ -98,7 +98,8 @@ serde-saphyr.workspace = true
serde.workspace = true serde.workspace = true
smallvec.workspace = true smallvec.workspace = true
smallstr.workspace = true smallstr.workspace = true
thiserror.workspace = true snafu.workspace = true
paste.workspace = true
tikv-jemallocator.optional = true tikv-jemallocator.optional = true
tikv-jemallocator.workspace = true tikv-jemallocator.workspace = true
tikv-jemalloc-ctl.optional = true tikv-jemalloc-ctl.optional = true

View file

@ -45,63 +45,162 @@ macro_rules! Err {
macro_rules! err { macro_rules! err {
(Request(Forbidden($level:ident!($($args:tt)+)))) => {{ (Request(Forbidden($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request {
$crate::ruma::api::client::error::ErrorKind::forbidden(), kind: $crate::ruma::api::client::error::ErrorKind::forbidden(),
$crate::err_log!(buf, $level, $($args)+), message: $crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST code: $crate::http::StatusCode::BAD_REQUEST,
) backtrace: Some($crate::snafu::Backtrace::capture()),
}
}}; }};
(Request(Forbidden($($args:tt)+))) => { (Request(Forbidden($($args:tt)+))) => {
$crate::error::Error::Request( {
$crate::ruma::api::client::error::ErrorKind::forbidden(), let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::format_maybe!($($args)+), $crate::error::Error::Request {
$crate::http::StatusCode::BAD_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)+)))) => {{ (Request($variant:ident($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request {
$crate::ruma::api::client::error::ErrorKind::$variant, kind: $crate::ruma::api::client::error::ErrorKind::$variant,
$crate::err_log!(buf, $level, $($args)+), message: $crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST code: $crate::http::StatusCode::BAD_REQUEST,
) backtrace: Some($crate::snafu::Backtrace::capture()),
}
}}; }};
(Request($variant:ident($($args:tt)+))) => { (Request($variant:ident($($args:tt)+))) => {
$crate::error::Error::Request( {
$crate::ruma::api::client::error::ErrorKind::$variant, let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::format_maybe!($($args)+), $crate::error::Error::Request {
$crate::http::StatusCode::BAD_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)+)) => {{ (Config($item:literal, $($args:tt)+)) => {{
let mut buf = String::new(); 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)+))) => {{ ($variant:ident($level:ident!($($args:tt)+))) => {{
let mut buf = String::new(); 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)+)) => { ($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)+)) => {{ ($level:ident!($($args:tt)+)) => {{
let mut buf = String::new(); 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)+) => { ($($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)+)); ($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset_all!(__CALLSITE.metadata().fields(), $($fields)+));
($out).into() std::borrow::Cow::<'static, str>::from($out)
}} }}
} }

View file

@ -6,151 +6,391 @@ mod serde;
use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError}; use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError};
use snafu::{IntoError, prelude::*};
pub use self::{err::visit, log::*}; pub use self::{err::visit, log::*};
#[derive(thiserror::Error)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum Error { pub enum Error {
#[error("PANIC!")] #[snafu(display("PANIC!"))]
PanicAny(Box<dyn Any + Send>), PanicAny {
#[error("PANIC! {0}")] panic: Box<dyn Any + Send>,
Panic(&'static str, Box<dyn Any + Send + 'static>), backtrace: snafu::Backtrace,
},
#[snafu(display("PANIC! {message}"))]
Panic {
message: &'static str,
panic: Box<dyn Any + Send + 'static>,
backtrace: snafu::Backtrace,
},
// std // std
#[error(transparent)] #[snafu(display("Format error: {source}"))]
Fmt(#[from] std::fmt::Error), Fmt {
#[error(transparent)] source: std::fmt::Error,
FromUtf8(#[from] std::string::FromUtf8Error), backtrace: snafu::Backtrace,
#[error("I/O error: {0}")] },
Io(#[from] std::io::Error),
#[error(transparent)] #[snafu(display("UTF-8 conversion error: {source}"))]
ParseFloat(#[from] std::num::ParseFloatError), FromUtf8 {
#[error(transparent)] source: std::string::FromUtf8Error,
ParseInt(#[from] std::num::ParseIntError), backtrace: snafu::Backtrace,
#[error(transparent)] },
Std(#[from] Box<dyn std::error::Error + Send>),
#[error(transparent)] #[snafu(display("I/O error: {source}"))]
ThreadAccessError(#[from] std::thread::AccessError), Io {
#[error(transparent)] source: std::io::Error,
TryFromInt(#[from] std::num::TryFromIntError), backtrace: snafu::Backtrace,
#[error(transparent)] },
TryFromSlice(#[from] std::array::TryFromSliceError),
#[error(transparent)] #[snafu(display("Parse float error: {source}"))]
Utf8(#[from] std::str::Utf8Error), 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<dyn std::error::Error + Send>,
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 // third-party
#[error(transparent)] #[snafu(display("Capacity error: {source}"))]
CapacityError(#[from] arrayvec::CapacityError), CapacityError {
#[error(transparent)] source: arrayvec::CapacityError,
CargoToml(#[from] cargo_toml::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
Clap(#[from] clap::error::Error),
#[error(transparent)] #[snafu(display("Cargo.toml error: {source}"))]
Extension(#[from] axum::extract::rejection::ExtensionRejection), CargoToml {
#[error(transparent)] source: cargo_toml::Error,
Figment(#[from] figment::error::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
Http(#[from] http::Error),
#[error(transparent)] #[snafu(display("Clap error: {source}"))]
HttpHeader(#[from] http::header::InvalidHeaderValue), Clap {
#[error("Join error: {0}")] source: clap::error::Error,
JoinError(#[from] tokio::task::JoinError), backtrace: snafu::Backtrace,
#[error(transparent)] },
Json(#[from] serde_json::Error),
#[error(transparent)] #[snafu(display("Extension rejection: {source}"))]
JsParseInt(#[from] ruma::JsParseIntError), // js_int re-export Extension {
#[error(transparent)] source: axum::extract::rejection::ExtensionRejection,
JsTryFromInt(#[from] ruma::JsTryFromIntError), // js_int re-export backtrace: snafu::Backtrace,
#[error(transparent)] },
Path(#[from] axum::extract::rejection::PathRejection),
#[error("Mutex poisoned: {0}")] #[snafu(display("Figment error: {source}"))]
Poison(Cow<'static, str>), Figment {
#[error("Regex error: {0}")] source: figment::error::Error,
Regex(#[from] regex::Error), backtrace: snafu::Backtrace,
#[error("Request error: {0}")] },
Reqwest(#[from] reqwest::Error),
#[error("{0}")] #[snafu(display("HTTP error: {source}"))]
SerdeDe(Cow<'static, str>), Http {
#[error("{0}")] source: http::Error,
SerdeSer(Cow<'static, str>), backtrace: snafu::Backtrace,
#[error(transparent)] },
TomlDe(#[from] toml::de::Error),
#[error(transparent)] #[snafu(display("Invalid HTTP header value: {source}"))]
TomlSer(#[from] toml::ser::Error), HttpHeader {
#[error("Tracing filter error: {0}")] source: http::header::InvalidHeaderValue,
TracingFilter(#[from] tracing_subscriber::filter::ParseError), backtrace: snafu::Backtrace,
#[error("Tracing reload error: {0}")] },
TracingReload(#[from] tracing_subscriber::reload::Error),
#[error(transparent)] #[snafu(display("Join error: {source}"))]
TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection), JoinError {
#[error(transparent)] source: tokio::task::JoinError,
YamlDe(#[from] serde_saphyr::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
YamlSer(#[from] serde_saphyr::ser_error::Error),
#[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 // ruma/conduwuit
#[error("Arithmetic operation failed: {0}")] #[snafu(display("Arithmetic operation failed: {message}"))]
Arithmetic(Cow<'static, str>), Arithmetic {
#[error("{0}: {1}")] message: Cow<'static, str>,
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove backtrace: snafu::Backtrace,
#[error("{0}")] },
BadServerResponse(Cow<'static, str>),
#[error(transparent)] #[snafu(display("{kind}: {message}"))]
CanonicalJson(#[from] ruma::CanonicalJsonError), BadRequest {
#[error("There was a problem with the '{0}' directive in your configuration: {1}")] kind: ruma::api::client::error::ErrorKind,
Config(&'static str, Cow<'static, str>), message: Cow<'static, str>,
#[error("{0}")] backtrace: snafu::Backtrace,
Conflict(Cow<'static, str>), // This is only needed for when a room alias already exists },
#[error(transparent)]
ContentDisposition(#[from] ruma::http_headers::ContentDispositionParseError), #[snafu(display("{message}"))]
#[error("{0}")] BadServerResponse {
Database(Cow<'static, str>), message: Cow<'static, str>,
#[error("Feature '{0}' is not available on this server.")] backtrace: snafu::Backtrace,
FeatureDisabled(Cow<'static, str>), },
#[error("Remote server {0} responded with: {1}")]
Federation(ruma::OwnedServerName, ruma::api::client::error::Error), #[snafu(display("Canonical JSON error: {source}"))]
#[error("{0} in {1}")] CanonicalJson {
InconsistentRoomState(&'static str, ruma::OwnedRoomId), source: ruma::CanonicalJsonError,
#[error(transparent)] backtrace: snafu::Backtrace,
IntoHttp(#[from] ruma::api::error::IntoHttpError), },
#[error("{0}")]
Ldap(Cow<'static, str>), #[snafu(display(
#[error(transparent)] "There was a problem with the '{directive}' directive in your configuration: {message}"
Mxc(#[from] ruma::MxcUriError), ))]
#[error(transparent)] Config {
Mxid(#[from] ruma::IdParseError), directive: &'static str,
#[error("from {0}: {1}")] message: Cow<'static, str>,
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError), backtrace: snafu::Backtrace,
#[error("{0}: {1}")] },
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode),
#[error(transparent)] #[snafu(display("{message}"))]
Ruma(#[from] ruma::api::client::error::Error), Conflict {
#[error(transparent)] message: Cow<'static, str>,
Signatures(#[from] ruma::signatures::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
StateRes(#[from] crate::state_res::Error),
#[error("uiaa")] #[snafu(display("Content disposition error: {source}"))]
Uiaa(ruma::api::client::uiaa::UiaaInfo), 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::Backtrace>,
},
#[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 // unique / untyped
#[error("{0}")] #[snafu(display("{message}"))]
Err(Cow<'static, str>), Err {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
} }
impl Error { impl Error {
#[inline] #[inline]
#[must_use] #[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] //#[deprecated]
#[must_use]
pub fn bad_database(message: &'static str) -> Self { 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. /// Sanitizes public-facing errors that can leak sensitive information.
pub fn sanitized_message(&self) -> String { pub fn sanitized_message(&self) -> String {
match self { match self {
| Self::Database(..) => String::from("Database error occurred."), | Self::Database { .. } => String::from("Database error occurred."),
| Self::Io(..) => String::from("I/O error occurred."), | Self::Io { .. } => String::from("I/O error occurred."),
| _ => self.message(), | _ => self.message(),
} }
} }
@ -158,8 +398,8 @@ impl Error {
/// Generate the error message string. /// Generate the error message string.
pub fn message(&self) -> String { pub fn message(&self) -> String {
match self { match self {
| Self::Federation(origin, error) => format!("Answer from {origin}: {error}"), | Self::Federation { server, error, .. } => format!("Answer from {server}: {error}"),
| Self::Ruma(error) => response::ruma_error_message(error), | Self::Ruma { source, .. } => response::ruma_error_message(source),
| _ => format!("{self}"), | _ => format!("{self}"),
} }
} }
@ -170,10 +410,10 @@ impl Error {
use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown}; use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown};
match self { match self {
| Self::Federation(_, error) | Self::Ruma(error) => | Self::Federation { error, .. } => response::ruma_error_kind(error).clone(),
response::ruma_error_kind(error).clone(), | Self::Ruma { source, .. } => response::ruma_error_kind(source).clone(),
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(), | Self::BadRequest { kind, .. } | Self::Request { kind, .. } => kind.clone(),
| Self::FeatureDisabled(..) => FeatureDisabled, | Self::FeatureDisabled { .. } => FeatureDisabled,
| _ => Unknown, | _ => Unknown,
} }
} }
@ -184,13 +424,15 @@ impl Error {
use http::StatusCode; use http::StatusCode;
match self { match self {
| Self::Federation(_, error) | Self::Ruma(error) => error.status_code, | Self::Federation { error, .. } => error.status_code,
| Self::Request(kind, _, code) => response::status_code(kind, *code), | Self::Ruma { source, .. } => source.status_code,
| Self::BadRequest(kind, ..) => response::bad_request_code(kind), | Self::Request { kind, code, .. } => response::status_code(kind, *code),
| Self::FeatureDisabled(..) => response::bad_request_code(&self.kind()), | Self::BadRequest { kind, .. } => response::bad_request_code(kind),
| Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), | Self::FeatureDisabled { .. } => response::bad_request_code(&self.kind()),
| Self::Conflict(_) => StatusCode::CONFLICT, | Self::Reqwest { source, .. } =>
| Self::Io(error) => response::io_error_code(error.kind()), 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, | _ => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }
@ -203,16 +445,46 @@ impl Error {
pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND } pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND }
} }
impl std::fmt::Debug for Error { // Debug is already derived by Snafu
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message()) /// 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<T> From<PoisonError<T>> for Error { impl<T> From<PoisonError<T>> for Error {
#[cold] #[cold]
#[inline(never)] #[inline(never)]
fn from(e: PoisonError<T>) -> Self { Self::Poison(e.to_string().into()) } fn from(e: PoisonError<T>) -> Self { PoisonSnafu { message: e.to_string() }.build() }
} }
#[allow(clippy::fallible_impl_from)] #[allow(clippy::fallible_impl_from)]
@ -224,6 +496,43 @@ impl From<Infallible> 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<T> From<arrayvec::CapacityError<T>> for Error {
fn from(_source: arrayvec::CapacityError<T>) -> Self {
let message: Cow<'static, str> = "capacity error: buffer is full".into();
ErrSnafu { message }.build()
}
}
#[cold] #[cold]
#[inline(never)] #[inline(never)]
pub fn infallible(_e: &Infallible) { pub fn infallible(_e: &Infallible) {

View file

@ -15,13 +15,16 @@ impl Error {
#[must_use] #[must_use]
#[inline] #[inline]
pub fn from_panic(e: Box<dyn Any + Send>) -> Self { Self::Panic(debug::panic_str(&e), e) } pub fn from_panic(e: Box<dyn Any + Send>) -> Self {
use super::PanicSnafu;
PanicSnafu { message: debug::panic_str(&e), panic: e }.build()
}
#[inline] #[inline]
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> { pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
match self { match self {
| Self::Panic(_, e) | Self::PanicAny(e) => e, | Self::Panic { panic, .. } | Self::PanicAny { panic, .. } => panic,
| Self::JoinError(e) => e.into_panic(), | Self::JoinError { source, .. } => source.into_panic(),
| _ => Box::new(self), | _ => Box::new(self),
} }
} }
@ -37,8 +40,8 @@ impl Error {
#[inline] #[inline]
pub fn is_panic(&self) -> bool { pub fn is_panic(&self) -> bool {
match &self { match &self {
| Self::Panic(..) | Self::PanicAny(..) => true, | Self::Panic { .. } | Self::PanicAny { .. } => true,
| Self::JoinError(e) => e.is_panic(), | Self::JoinError { source, .. } => source.is_panic(),
| _ => false, | _ => false,
} }
} }

View file

@ -47,8 +47,8 @@ impl axum::response::IntoResponse for Error {
impl From<Error> for UiaaResponse { impl From<Error> for UiaaResponse {
#[inline] #[inline]
fn from(error: Error) -> Self { fn from(error: Error) -> Self {
if let Error::Uiaa(uiaainfo) = error { if let Error::Uiaa { info, .. } = error {
return Self::AuthResponse(uiaainfo); return Self::AuthResponse(info);
} }
let body = ErrorBody::Standard { let body = ErrorBody::Standard {

View file

@ -5,9 +5,15 @@ use serde::{de, ser};
use crate::Error; use crate::Error;
impl de::Error for Error { impl de::Error for Error {
fn custom<T: Display + ToString>(msg: T) -> Self { Self::SerdeDe(msg.to_string().into()) } fn custom<T: Display + ToString>(msg: T) -> Self {
let message: std::borrow::Cow<'static, str> = msg.to_string().into();
super::SerdeDeSnafu { message }.build()
}
} }
impl ser::Error for Error { impl ser::Error for Error {
fn custom<T: Display + ToString>(msg: T) -> Self { Self::SerdeSer(msg.to_string().into()) } fn custom<T: Display + ToString>(msg: T) -> Self {
let message: std::borrow::Cow<'static, str> = msg.to_string().into();
super::SerdeSerSnafu { message }.build()
}
} }

View file

@ -1,7 +1,7 @@
use ruma::{RoomVersionId, canonical_json::redact_content_in_place}; use ruma::{RoomVersionId, canonical_json::redact_content_in_place};
use serde_json::{Value as JsonValue, json, value::to_raw_value}; 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)] #[implement(super::Pdu)]
pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result { 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()) let mut content = serde_json::from_str(self.content.get())
.map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?; .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()) redact_content_in_place(&mut content, room_version_id, self.kind.to_string()).map_err(
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?; |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"); let reason = serde_json::to_value(reason).expect("Failed to preserialize reason");

View file

@ -27,7 +27,7 @@ use serde_json::{
use crate::{ use crate::{
matrix::{Event, Pdu, pdu::EventHash}, 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); static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
@ -170,10 +170,12 @@ struct TestStore<E: Event>(HashMap<OwnedEventId, E>);
#[allow(unused)] #[allow(unused)]
impl<E: Event + Clone> TestStore<E> { impl<E: Event + Clone> TestStore<E> {
fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> { fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> {
self.0 self.0.get(event_id).cloned().ok_or_else(|| {
.get(event_id) NotFoundSnafu {
.cloned() message: format!("{} not found", event_id),
.ok_or_else(|| Error::NotFound(format!("{} not found", event_id))) }
.build()
})
} }
/// Returns the events that correspond to the `event_ids` sorted in the same /// Returns the events that correspond to the `event_ids` sorted in the same

View file

@ -1,23 +1,40 @@
use serde_json::Error as JsonError; use serde_json::Error as JsonError;
use thiserror::Error; use snafu::{IntoError, prelude::*};
/// Represents the various errors that arise when resolving state. /// Represents the various errors that arise when resolving state.
#[derive(Error, Debug)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// A deserialization error. /// A deserialization error.
#[error(transparent)] #[snafu(display("JSON error: {source}"))]
SerdeJson(#[from] JsonError), SerdeJson {
source: JsonError,
backtrace: snafu::Backtrace,
},
/// The given option or version is unsupported. /// The given option or version is unsupported.
#[error("Unsupported room version: {0}")] #[snafu(display("Unsupported room version: {version}"))]
Unsupported(String), Unsupported {
version: String,
backtrace: snafu::Backtrace,
},
/// The given event was not found. /// The given event was not found.
#[error("Not found error: {0}")] #[snafu(display("Not found error: {message}"))]
NotFound(String), NotFound {
message: String,
backtrace: snafu::Backtrace,
},
/// Invalid fields in the given PDU. /// Invalid fields in the given PDU.
#[error("Invalid PDU: {0}")] #[snafu(display("Invalid PDU: {message}"))]
InvalidPdu(String), InvalidPdu {
message: String,
backtrace: snafu::Backtrace,
},
}
impl From<serde_json::Error> for Error {
fn from(source: serde_json::Error) -> Self { SerdeJsonSnafu.into_error(source) }
} }

View file

@ -24,6 +24,7 @@ use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue};
use super::{ use super::{
Error, Event, Result, StateEventType, StateKey, TimelineEventType, Error, Event, Result, StateEventType, StateKey, TimelineEventType,
error::InvalidPduSnafu,
power_levels::{ power_levels::{
deserialize_power_levels, deserialize_power_levels_content_fields, deserialize_power_levels, deserialize_power_levels_content_fields,
deserialize_power_levels_content_invite, deserialize_power_levels_content_redact, deserialize_power_levels_content_invite, deserialize_power_levels_content_redact,
@ -383,8 +384,8 @@ where
return Ok(false); return Ok(false);
} }
let target_user = let target_user = <&UserId>::try_from(state_key)
<&UserId>::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{e}")))?; .map_err(|e| InvalidPduSnafu { message: format!("{e}") }.build())?;
let user_for_join_auth = content let user_for_join_auth = content
.join_authorised_via_users_server .join_authorised_via_users_server
@ -461,7 +462,7 @@ where
?sender_membership_event_content, ?sender_membership_event_content,
"Sender membership event content missing membership field" "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()?; let membership_state = membership_state.deserialize()?;

View file

@ -29,18 +29,18 @@ use ruma::{
}; };
use serde_json::from_str as from_json_str; 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; use self::power_levels::PowerLevelsContentFields;
pub use self::{ pub use self::{
event_auth::{auth_check, auth_types_for_event}, event_auth::{auth_check, auth_types_for_event},
room_version::RoomVersion, room_version::RoomVersion,
}; };
use super::{Event, StateKey};
use crate::{ use crate::{
debug, debug_error, err, debug, debug_error,
matrix::{Event, StateKey},
state_res::room_version::StateResolutionVersion, state_res::room_version::StateResolutionVersion,
trace, trace,
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt}, utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt},
warn, warn,
}; };
@ -118,7 +118,10 @@ where
let csg = calculate_conflicted_subgraph(&conflicting, event_fetch) let csg = calculate_conflicted_subgraph(&conflicting, event_fetch)
.await .await
.ok_or_else(|| { .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"); debug!(count = csg.len(), "conflicted subgraph");
trace!(set = ?csg, "conflicted subgraph"); trace!(set = ?csg, "conflicted subgraph");
@ -149,10 +152,11 @@ where
let control_events: Vec<_> = all_conflicted let control_events: Vec<_> = all_conflicted
.iter() .iter()
.stream() .stream()
.wide_filter_map(async |id| { .broad_filter_map(async |id| {
is_power_event_id(id, &event_fetch) event_fetch(id.clone())
.await .await
.then_some(id.clone()) .filter(|event| is_power_event(&event))
.map(|_| id.clone())
}) })
.collect() .collect()
.await; .await;
@ -314,7 +318,10 @@ where
trace!(event_id = event_id.as_str(), "fetching event for its auth events"); trace!(event_id = event_id.as_str(), "fetching event for its auth events");
let evt = fetch_event(event_id.clone()).await; let evt = fetch_event(event_id.clone()).await;
if evt.is_none() { 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(); path.pop();
continue; continue;
} }
@ -402,11 +409,11 @@ where
let fetcher = async |event_id: OwnedEventId| { let fetcher = async |event_id: OwnedEventId| {
let pl = *event_to_pl let pl = *event_to_pl
.get(&event_id) .get(&event_id)
.ok_or_else(|| Error::NotFound(String::new()))?; .ok_or_else(|| NotFoundSnafu { message: "" }.build())?;
let ev = fetch_event(event_id) let ev = fetch_event(event_id)
.await .await
.ok_or_else(|| Error::NotFound(String::new()))?; .ok_or_else(|| NotFoundSnafu { message: "" }.build())?;
Ok((pl, ev.origin_server_ts())) Ok((pl, ev.origin_server_ts()))
}; };
@ -612,9 +619,12 @@ where
let events_to_check: Vec<_> = events_to_check let events_to_check: Vec<_> = events_to_check
.map(Result::Ok) .map(Result::Ok)
.broad_and_then(async |event_id| { .broad_and_then(async |event_id| {
fetch_event(event_id.to_owned()) fetch_event(event_id.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu {
.ok_or_else(|| Error::NotFound(format!("Failed to find {event_id}"))) message: format!("Failed to find {event_id}"),
}
.build()
})
}) })
.try_collect() .try_collect()
.boxed() .boxed()
@ -653,7 +663,7 @@ where
trace!(event_id = event.event_id().as_str(), "checking event"); trace!(event_id = event.event_id().as_str(), "checking event");
let state_key = event let state_key = event
.state_key() .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( let auth_types = auth_types_for_event(
event.event_type(), event.event_type(),
@ -669,13 +679,14 @@ where
trace!("room version uses hashed IDs, manually fetching create event"); 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_raw = event.room_id_or_hash().as_str().replace('!', "$");
let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| { let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| {
Error::InvalidPdu(format!( InvalidPduSnafu {
"Failed to parse create event ID from room ID/hash: {e}" 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); auth_state.insert(create_event.event_type().with_state_key(""), create_event);
} }
for aid in event.auth_events() { for aid in event.auth_events() {
@ -686,7 +697,7 @@ where
auth_state.insert( auth_state.insert(
ev.event_type() ev.event_type()
.with_state_key(ev.state_key().ok_or_else(|| { .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(), ev.clone(),
); );
@ -801,13 +812,13 @@ where
let event = fetch_event(p.clone()) let event = fetch_event(p.clone())
.await .await
.ok_or_else(|| Error::NotFound(format!("Failed to find {p}")))?; .ok_or_else(|| NotFoundSnafu { message: format!("Failed to find {p}") }.build())?;
pl = None; pl = None;
for aid in event.auth_events() { for aid in event.auth_events() {
let ev = fetch_event(aid.to_owned()) let ev = fetch_event(aid.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu { message: format!("Failed to find {aid}") }.build()
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?; })?;
if is_type_and_key(&ev, &TimelineEventType::RoomPowerLevels, "") { if is_type_and_key(&ev, &TimelineEventType::RoomPowerLevels, "") {
pl = Some(aid.to_owned()); pl = Some(aid.to_owned());
@ -869,9 +880,9 @@ where
event = None; event = None;
for aid in sort_ev.auth_events() { for aid in sort_ev.auth_events() {
let aev = fetch_event(aid.to_owned()) let aev = fetch_event(aid.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu { message: format!("Failed to find {aid}") }.build()
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?; })?;
if is_type_and_key(&aev, &TimelineEventType::RoomPowerLevels, "") { if is_type_and_key(&aev, &TimelineEventType::RoomPowerLevels, "") {
event = Some(aev); event = Some(aev);
@ -915,6 +926,7 @@ async fn add_event_and_auth_chain_to_graph<E, F, Fut>(
} }
} }
#[allow(dead_code)]
async fn is_power_event_id<E, F, Fut>(event_id: &EventId, fetch: &F) -> bool async fn is_power_event_id<E, F, Fut>(event_id: &EventId, fetch: &F) -> bool
where where
F: Fn(OwnedEventId) -> Fut + Sync, F: Fn(OwnedEventId) -> Fut + Sync,

View file

@ -1,6 +1,6 @@
use ruma::RoomVersionId; use ruma::RoomVersionId;
use super::{Error, Result}; use super::{Result, error::UnsupportedSnafu};
#[derive(Debug)] #[derive(Debug)]
#[allow(clippy::exhaustive_enums)] #[allow(clippy::exhaustive_enums)]
@ -163,7 +163,11 @@ impl RoomVersion {
| RoomVersionId::V10 => Self::V10, | RoomVersionId::V10 => Self::V10,
| RoomVersionId::V11 => Self::V11, | RoomVersionId::V11 => Self::V11,
| RoomVersionId::V12 => Self::V12, | RoomVersionId::V12 => Self::V12,
| ver => return Err(Error::Unsupported(format!("found version `{ver}`"))), | ver =>
return Err(UnsupportedSnafu {
version: format!("found version `{ver}`"),
}
.build()),
}) })
} }
} }

View file

@ -22,7 +22,7 @@ use serde_json::{
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value}, 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::{ use crate::{
Result, RoomVersion, info, Result, RoomVersion, info,
matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash}, matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
@ -232,7 +232,7 @@ impl<E: Event + Clone> TestStore<E> {
self.0 self.0
.get(event_id) .get(event_id)
.cloned() .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) .map_err(Into::into)
} }

View file

@ -14,9 +14,11 @@ pub mod utils;
pub use ::arrayvec; pub use ::arrayvec;
pub use ::http; pub use ::http;
pub use ::paste;
pub use ::ruma; pub use ::ruma;
pub use ::smallstr; pub use ::smallstr;
pub use ::smallvec; pub use ::smallvec;
pub use ::snafu;
pub use ::toml; pub use ::toml;
pub use ::tracing; pub use ::tracing;
pub use config::Config; pub use config::Config;

View file

@ -2,6 +2,8 @@
use std::{cell::Cell, fmt::Debug, path::PathBuf, sync::LazyLock}; use std::{cell::Cell, fmt::Debug, path::PathBuf, sync::LazyLock};
use snafu::IntoError;
use crate::{Result, is_equal_to}; use crate::{Result, is_equal_to};
type Id = usize; type Id = usize;
@ -142,7 +144,9 @@ pub fn getcpu() -> Result<usize> {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
#[inline] #[inline]
pub fn getcpu() -> Result<usize> { Err(crate::Error::Io(std::io::ErrorKind::Unsupported.into())) } pub fn getcpu() -> Result<usize> {
Err(crate::error::IoSnafu.into_error(std::io::ErrorKind::Unsupported.into()))
}
fn query_cores_available() -> impl Iterator<Item = Id> { fn query_cores_available() -> impl Iterator<Item = Id> {
core_affinity::get_core_ids() core_affinity::get_core_ids()

View file

@ -255,7 +255,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
| "$serde_json::private::RawValue" => visitor.visit_map(self), | "$serde_json::private::RawValue" => visitor.visit_map(self),
| "Cbor" => visitor | "Cbor" => visitor
.visit_newtype_struct(&mut minicbor_serde::Deserializer::new(self.record_trail())) .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), | _ => 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 end = self.pos.saturating_add(BYTES).min(self.buf.len());
let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?; let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?;
let bytes = bytes let bytes = bytes.into_inner().map_err(|_| {
.into_inner() let message: std::borrow::Cow<'static, str> = "i64 buffer underflow".into();
.map_err(|_| Self::Error::SerdeDe("i64 buffer underflow".into()))?; conduwuit_core::error::SerdeDeSnafu { message }.build()
})?;
self.inc_pos(BYTES); self.inc_pos(BYTES);
visitor.visit_i64(i64::from_be_bytes(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 end = self.pos.saturating_add(BYTES).min(self.buf.len());
let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?; let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?;
let bytes = bytes let bytes = bytes.into_inner().map_err(|_| {
.into_inner() let message: std::borrow::Cow<'static, str> = "u64 buffer underflow".into();
.map_err(|_| Self::Error::SerdeDe("u64 buffer underflow".into()))?; conduwuit_core::error::SerdeDeSnafu { message }.build()
})?;
self.inc_pos(BYTES); self.inc_pos(BYTES);
visitor.visit_u64(u64::from_be_bytes(bytes)) visitor.visit_u64(u64::from_be_bytes(bytes))

View file

@ -199,7 +199,10 @@ impl<W: Write> ser::Serializer for &mut Serializer<'_, W> {
value value
.serialize(&mut Serializer::new(&mut Writer::new(&mut self.out))) .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:?}"), | _ => unhandled!("Unrecognized serialization Newtype {name:?}"),
} }

View file

@ -1,4 +1,4 @@
use std::sync::Arc; use std::{borrow::Cow, sync::Arc};
use axum::{Router, response::IntoResponse}; use axum::{Router, response::IntoResponse};
use conduwuit::Error; use conduwuit::Error;
@ -18,5 +18,10 @@ pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
} }
async fn not_found(_uri: Uri) -> impl IntoResponse { 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,
}
} }

View file

@ -147,11 +147,11 @@ impl Service {
// same appservice) // same appservice)
if let Ok(existing) = self.find_from_token(&registration.as_token).await { if let Ok(existing) = self.find_from_token(&registration.as_token).await {
if existing.registration.id != registration.id { if existing.registration.id != registration.id {
return Err(err!(Request(InvalidParam( return Err!(Request(InvalidParam(
"Cannot register appservice: Token is already used by appservice '{}'. \ "Cannot register appservice: Token is already used by appservice '{}'. \
Please generate a different token.", Please generate a different token.",
existing.registration.id existing.registration.id
)))); )));
} }
} }
@ -163,10 +163,10 @@ impl Service {
.await .await
.is_ok() .is_ok()
{ {
return Err(err!(Request(InvalidParam( return Err!(Request(InvalidParam(
"Cannot register appservice: The provided token is already in use by a user \ "Cannot register appservice: The provided token is already in use by a user \
device. Please generate a different token for the appservice." device. Please generate a different token for the appservice."
)))); )));
} }
self.db self.db

View file

@ -2,7 +2,7 @@ use std::{fmt::Debug, mem};
use bytes::Bytes; use bytes::Bytes;
use conduwuit::{ 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, error::inspect_debug_log, implement, trace,
}; };
use http::{HeaderValue, header::AUTHORIZATION}; use http::{HeaderValue, header::AUTHORIZATION};
@ -179,10 +179,7 @@ async fn into_http_response(
debug!("Got {status:?} for {method} {url}"); debug!("Got {status:?} for {method} {url}");
if !status.is_success() { if !status.is_success() {
return Err(Error::Federation( return Err!(Federation(dest.to_owned(), RumaError::from_http_response(http_response),));
dest.to_owned(),
RumaError::from_http_response(http_response),
));
} }
Ok(http_response) Ok(http_response)

View file

@ -35,7 +35,7 @@ pub async fn fetch_remote_thumbnail(
.fetch_thumbnail_authenticated(mxc, user, server, timeout_ms, dim) .fetch_thumbnail_authenticated(mxc, user, server, timeout_ms, dim)
.await; .await;
if let Err(Error::Request(NotFound, ..)) = &result { if let Err(Error::Request { kind: NotFound, .. }) = &result {
return self return self
.fetch_thumbnail_unauthenticated(mxc, user, server, timeout_ms, dim) .fetch_thumbnail_unauthenticated(mxc, user, server, timeout_ms, dim)
.await; .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 return self
.fetch_content_unauthenticated(mxc, user, server, timeout_ms) .fetch_content_unauthenticated(mxc, user, server, timeout_ms)
.await; .await;

View file

@ -112,7 +112,14 @@ where
{ {
let event_fetch = |event_id| self.event_fetch(event_id); let event_fetch = |event_id| self.event_fetch(event_id);
let event_exists = |event_id| self.event_exists(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) Ok(
.map_err(|e| err!(error!("State resolution failed: {e:?}"))) state_res::resolve(
.await room_version,
state_sets,
auth_chain_sets,
&event_fetch,
&event_exists,
)
.await?,
)
} }

View file

@ -3,7 +3,7 @@ use std::{
str::FromStr, str::FromStr,
}; };
use conduwuit::{Error, Result}; use conduwuit::{Err, Error, Result};
use ruma::{UInt, api::client::error::ErrorKind}; use ruma::{UInt, api::client::error::ErrorKind};
use crate::rooms::short::ShortRoomId; use crate::rooms::short::ShortRoomId;
@ -57,7 +57,7 @@ impl FromStr for PaginationToken {
if let Some(token) = pag_tok() { if let Some(token) = pag_tok() {
Ok(token) Ok(token)
} else { } else {
Err(Error::BadRequest(ErrorKind::InvalidParam, "invalid token")) Err!(BadRequest(ErrorKind::InvalidParam, "invalid token"))
} }
} }
} }

View file

@ -75,10 +75,7 @@ pub async fn create_hash_and_sign_event(
let content: RoomCreateEventContent = serde_json::from_str(content.get())?; let content: RoomCreateEventContent = serde_json::from_str(content.get())?;
Ok(content.room_version) Ok(content.room_version)
} else { } else {
Err(Error::InconsistentRoomState( Err!(InconsistentRoomState("non-create event for room of unknown version", room_id))
"non-create event for room of unknown version",
room_id,
))
} }
} }
let PduBuilder { let PduBuilder {
@ -275,7 +272,9 @@ pub async fn create_hash_and_sign_event(
.hash_and_sign_event(&mut pdu_json, &room_version_id) .hash_and_sign_event(&mut pdu_json, &room_version_id)
{ {
return match e { 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(TooLarge("Message/PDU is too long (exceeds 65535 bytes)")))
}, },
| _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))), | _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))),

View file

@ -1,7 +1,7 @@
use std::{collections::BTreeMap, sync::Arc}; use std::{collections::BTreeMap, sync::Arc};
use conduwuit::{ use conduwuit::{
Err, Error, Result, SyncRwLock, err, error, implement, utils, Err, Result, SyncRwLock, err, error, implement, utils,
utils::{hash, string::EMPTY}, utils::{hash, string::EMPTY},
}; };
use database::{Deserialized, Json, Map}; use database::{Deserialized, Json, Map};
@ -117,7 +117,7 @@ pub async fn try_auth(
} else if let Some(username) = user { } else if let Some(username) = user {
username username
} else { } else {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::Unrecognized, ErrorKind::Unrecognized,
"Identifier type not recognized.", "Identifier type not recognized.",
)); ));
@ -125,7 +125,7 @@ pub async fn try_auth(
#[cfg(not(feature = "element_hacks"))] #[cfg(not(feature = "element_hacks"))]
let Some(UserIdentifier::UserIdOrLocalpart(username)) = identifier else { let Some(UserIdentifier::UserIdOrLocalpart(username)) = identifier else {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::Unrecognized, ErrorKind::Unrecognized,
"Identifier type not recognized.", "Identifier type not recognized.",
)); ));
@ -135,7 +135,7 @@ pub async fn try_auth(
username.clone(), username.clone(),
self.services.globals.server_name(), 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 // Check if the access token being used matches the credentials used for UIAA
if user_id.localpart() != user_id_from_username.localpart() { if user_id.localpart() != user_id_from_username.localpart() {

View file

@ -761,13 +761,13 @@ impl Service {
.keys .keys
.into_values(); .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, ErrorKind::InvalidParam,
"Self signing key contained no key.", "Self signing key contained no key.",
))?; )))?;
if self_signing_key_ids.next().is_some() { if self_signing_key_ids.next().is_some() {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Self signing key contained more than one key.", "Self signing key contained more than one key.",
)); ));
@ -1439,13 +1439,13 @@ pub fn parse_master_key(
let master_key = master_key let master_key = master_key
.deserialize() .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 mut master_key_ids = master_key.keys.values();
let master_key_id = master_key_ids let master_key_id = master_key_ids
.next() .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() { if master_key_ids.next().is_some() {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Master key contained more than one key.", "Master key contained more than one key.",
)); ));

View file

@ -25,7 +25,7 @@ axum.workspace = true
futures.workspace = true futures.workspace = true
tracing.workspace = true tracing.workspace = true
rand.workspace = true rand.workspace = true
thiserror.workspace = true snafu.workspace = true
[lints] [lints]
workspace = true workspace = true

View file

@ -8,6 +8,7 @@ use axum::{
}; };
use conduwuit_build_metadata::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL, version_tag}; use conduwuit_build_metadata::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL, version_tag};
use conduwuit_service::state; use conduwuit_service::state;
use snafu::{IntoError, prelude::*};
pub fn build() -> Router<state::State> { pub fn build() -> Router<state::State> {
Router::<state::State>::new() Router::<state::State>::new()
@ -48,10 +49,17 @@ async fn logo_handler() -> impl IntoResponse {
) )
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, Snafu)]
enum WebError { enum WebError {
#[error("Failed to render template: {0}")] #[snafu(display("Failed to render template: {source}"))]
Render(#[from] askama::Error), Render {
source: askama::Error,
backtrace: snafu::Backtrace,
},
}
impl From<askama::Error> for WebError {
fn from(source: askama::Error) -> Self { RenderSnafu.into_error(source) }
} }
impl IntoResponse for WebError { impl IntoResponse for WebError {
@ -66,7 +74,7 @@ impl IntoResponse for WebError {
let nonce = rand::random::<u64>().to_string(); let nonce = rand::random::<u64>().to_string();
let status = match &self { let status = match &self {
| Self::Render(_) => StatusCode::INTERNAL_SERVER_ERROR, | Self::Render { .. } => StatusCode::INTERNAL_SERVER_ERROR,
}; };
let tmpl = Error { nonce: &nonce, err: self }; let tmpl = Error { nonce: &nonce, err: self };
if let Ok(body) = tmpl.render() { if let Ok(body) = tmpl.render() {

View file

@ -12,8 +12,8 @@ Server Error
</h1> </h1>
{%- match err -%} {%- match err -%}
{% when WebError::Render(err) -%} {% when WebError::Render { source, .. } -%}
<pre>{{ err }}</pre> <pre>{{ source }}</pre>
{% else -%} <p>An error occurred</p> {% else -%} <p>An error occurred</p>
{%- endmatch -%} {%- endmatch -%}