Commit 9a25b6ce66f4
tarball4 files changed+96-5
@@ -20,11 +20,17 @@ rooms, connection, encryption, verification, upload progress, session. no
2020
command results ride on it. drain it from its own task, it's bounded and the2121
engine waits for you2222
23
-one error type for everything, `Error { kind, message, challenge }`. match on24
-`kind`. `Interrupted` means the engine stopped, the room closed, or the task25
-doing the work died before it could answer. `challenge` turns up with23
+one error type for everything, `Error { kind, message, challenge, server }`.24
+match on `kind`. `Interrupted` means the engine stopped, the room closed, or the25
+task doing the work died before it could answer. `challenge` turns up with2626
`InteractiveAuthRequired`2727
28
+`message` is ours and says what we were doing. `server` is the homeserver's29
+errcode and message, dug out of the source chain when the error is built, so a30
+host can show "invalid password" without walking the chain. `AccountError`31
+carries the same, plus a `kind`. neither folds it into `Display`, since anything32
+printing the chain would say it twice33
+2834
## engine2935
3036
the run loop routes and does nothing slow. every task it spawns is registered@@ -25,7 +25,8 @@ use tokio::sync::{broadcast, mpsc};
2525
use url::Url;2626
2727
use crate::{28
- ClientHandle, Engine, EngineConfig, EngineError, ErrorKind, EventStream, SessionStore,28
+ ClientHandle, Engine, EngineConfig, EngineError, ErrorKind, EventStream, ServerError,29
+ SessionStore,2930
engine::{Persistence, StoreLock},3031
error::SourceError,3132
};@@ -348,6 +349,8 @@ pub enum AccountError {
348349
#[error("{context} failed")]349350
Operation {350351
context: &'static str,352
+ kind: ErrorKind,353
+ server: Option<ServerError>,351354
#[source]352355
source: Box<SourceError>,353356
},@@ -376,6 +379,18 @@ pub enum AccountError {
376379
Engine(#[from] EngineError),377380
}378381
382
+impl AccountError {383
+ /// what the homeserver said, when it refused384
+ #[must_use]385
+ pub fn server(&self) -> Option<&ServerError> {386
+ match self {387
+ Self::Operation { server, .. } => server.as_ref(),388
+ Self::Unsaved { source, .. } => source.server(),389
+ _ => None,390
+ }391
+ }392
+}393
+379394
/// a live login whose record never reached its `SessionStore`380395
pub struct UnsavedSession {381396
session: Session,@@ -684,6 +699,8 @@ pub(crate) fn account_error(
684699
) -> AccountError {685700
AccountError::Operation {686701
context,702
+ kind: crate::error::classify(&source),703
+ server: ServerError::find(&source),687704
source: Box::new(source),688705
}689706
}@@ -33,6 +33,8 @@ pub struct Error {
3333
pub message: String,3434
/// set when the server wants interactive auth before it'll do the thing3535
pub challenge: Option<Box<InteractiveAuthChallenge>>,36
+ /// set when the homeserver refused with a matrix error37
+ pub server: Option<ServerError>,3638
#[source]3739
#[cfg_attr(feature = "serde", serde(skip))]3840
source: Option<Arc<SourceError>>,@@ -68,12 +70,76 @@ pub enum ErrorKind {
6870
Other,6971
}7072
73
+/// the homeserver's own answer, worth showing a person as it stands74
+#[derive(Clone, Debug, Eq, PartialEq)]75
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]76
+#[non_exhaustive]77
+pub struct ServerError {78
+ pub status: u16,79
+ pub errcode: String,80
+ pub message: String,81
+}82
+83
+impl ServerError {84
+ pub(crate) fn find(source: &(dyn std::error::Error + 'static)) -> Option<Self> {85
+ let mut current = Some(source);86
+ while let Some(error) = current {87
+ let api = if let Some(error) = error.downcast_ref::<matrix_sdk::Error>() {88
+ error.as_client_api_error()89
+ } else if let Some(error) = error.downcast_ref::<HttpError>() {90
+ error.as_client_api_error()91
+ } else {92
+ error.downcast_ref::<ruma::api::error::Error>()93
+ };94
+ if let Some(api) = api95
+ && let ruma::api::error::ErrorBody::Standard(body) = &api.body96
+ {97
+ return Some(Self {98
+ status: api.status_code.as_u16(),99
+ errcode: body.kind.errcode().to_string(),100
+ message: body.message.clone(),101
+ });102
+ }103
+ current = error.source();104
+ }105
+ None106
+ }107
+}108
+109
+impl std::fmt::Display for ServerError {110
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {111
+ write!(formatter, "{} ({})", self.message, self.errcode)112
+ }113
+}114
+115
+#[cfg(any(feature = "sqlite", feature = "indexeddb"))]116
+pub(crate) fn classify(source: &(dyn std::error::Error + 'static)) -> ErrorKind {117
+ let mut current = Some(source);118
+ while let Some(error) = current {119
+ if let Some(error) = error.downcast_ref::<matrix_sdk::Error>() {120
+ return classify_sdk_error(error);121
+ }122
+ if let Some(error) = error.downcast_ref::<HttpError>() {123
+ return classify_http_error(error);124
+ }125
+ if let Some(error) = error.downcast_ref::<OAuthError>() {126
+ return classify_oauth_error(error);127
+ }128
+ if let Some(error) = error.downcast_ref::<RefreshTokenError>() {129
+ return classify_refresh_token_error(error);130
+ }131
+ current = error.source();132
+ }133
+ ErrorKind::Other134
+}135
+71136
impl Error {72137
pub(crate) fn new(kind: ErrorKind, message: impl Into<String>) -> Self {73138
Self {74139
kind,75140
message: message.into(),76141
challenge: None,142
+ server: None,77143
source: None,78144
}79145
}@@ -84,6 +150,7 @@ impl Error {
84150
source: impl std::error::Error + SendOutsideWasm + SyncOutsideWasm + 'static,85151
) -> Self {86152
Self {153
+ server: ServerError::find(&source),87154
source: Some(Arc::new(source)),88155
..Self::new(kind, message)89156
}@@ -174,6 +241,7 @@ impl PartialEq for Error {
174241
self.kind == other.kind175242
&& self.message == other.message176243
&& self.challenge == other.challenge244
+ && self.server == other.server177245
}178246
}179247
@@ -34,7 +34,7 @@ pub use account::{
3434
pub use session_store::{EncryptedSessionStore, SessionStore};3535
3636
pub use engine::{Engine, EngineConfig, EngineError};37
-pub use error::{Error, ErrorKind};37
+pub use error::{Error, ErrorKind, ServerError};3838
pub use imbl;3939
#[cfg(feature = "mirror")]4040
pub use mirror::{