atagen

macltok

public
Star0
Watch0
9953b20c51159e75754b0020343e54e1e20b6c53
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/error.rsRust327 lines11.4 KB
1
use std::sync::Arc;
2
3
use matrix_sdk::{
4
    HttpError, RefreshTokenError, SendOutsideWasm, SyncOutsideWasm,
5
    authentication::oauth::error::{
6
        OAuthAuthorizationCodeError, OAuthDiscoveryError, OAuthError, OAuthTokenRevocationError,
7
        RequestTokenError,
8
    },
9
    encryption::{
10
        identities::{ManualVerifyError, RequestVerificationError},
11
        recovery::RecoveryError,
12
        secret_storage::{ImportError, SecretStorageError},
13
    },
14
};
15
#[cfg(feature = "serde")]
16
use serde::{Deserialize, Serialize};
17
18
use crate::InteractiveAuthChallenge;
19
20
/// the sdk's `SendOutsideWasm` isn't an auto trait, so it can't go in a trait object
21
#[cfg(not(target_family = "wasm"))]
22
pub(crate) type SourceError = dyn std::error::Error + Send + Sync;
23
#[cfg(target_family = "wasm")]
24
pub(crate) type SourceError = dyn std::error::Error;
25
26
/// the one thing that goes wrong, for commands and queries alike. match on `kind`, show `message`
27
#[derive(Clone, Debug, thiserror::Error)]
28
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29
#[error("{message}")]
30
#[non_exhaustive]
31
pub struct Error {
32
    pub kind: ErrorKind,
33
    pub message: String,
34
    /// set when the server wants interactive auth before it'll do the thing
35
    pub challenge: Option<Box<InteractiveAuthChallenge>>,
36
    /// set when the homeserver refused with a matrix error
37
    pub server: Option<ServerError>,
38
    #[source]
39
    #[cfg_attr(feature = "serde", serde(skip))]
40
    source: Option<Arc<SourceError>>,
41
}
42
43
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45
pub enum ErrorKind {
46
    /// the engine stopped, or the task doing the work died, before answering
47
    Interrupted,
48
    Unauthenticated,
49
    InteractiveAuthRequired,
50
    Invalid,
51
    /// the room's power levels don't let you
52
    Forbidden,
53
    RoomUnknown,
54
    RoomNotOpen,
55
    TimelineNotOpen,
56
    EventContextNotOpen,
57
    SearchNotOpen,
58
    EventNotFound,
59
    RoomBusy,
60
    QueueFull,
61
    EmptyBody,
62
    MediaUnknown,
63
    MediaTooLarge,
64
    MediaUnavailable,
65
    VerificationInactive,
66
    VerificationUnsupported,
67
    NoCrossSigningIdentity,
68
    RecoveryRejected,
69
    /// the server already holds a key backup, which recovery has to open or replace first
70
    KeyBackupExists,
71
    Network,
72
    /// the homeserver wants us to slow down. try again later
73
    RateLimited,
74
    Server,
75
    Store,
76
    Timeline,
77
    Encryption,
78
    Other,
79
}
80
81
/// the homeserver's own answer, worth showing a person as it stands
82
#[derive(Clone, Debug, Eq, PartialEq)]
83
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
84
#[non_exhaustive]
85
pub struct ServerError {
86
    pub status: u16,
87
    pub errcode: String,
88
    pub message: String,
89
}
90
91
impl ServerError {
92
    pub(crate) fn find(source: &(dyn std::error::Error + 'static)) -> Option<Self> {
93
        let mut current = Some(source);
94
        while let Some(error) = current {
95
            let api = if let Some(error) = error.downcast_ref::<matrix_sdk::Error>() {
96
                error.as_client_api_error()
97
            } else if let Some(error) = error.downcast_ref::<HttpError>() {
98
                error.as_client_api_error()
99
            } else {
100
                error.downcast_ref::<ruma::api::error::Error>()
101
            };
102
            if let Some(api) = api
103
                && let ruma::api::error::ErrorBody::Standard(body) = &api.body
104
            {
105
                return Some(Self {
106
                    status: api.status_code.as_u16(),
107
                    errcode: body.kind.errcode().to_string(),
108
                    message: body.message.clone(),
109
                });
110
            }
111
            current = error.source();
112
        }
113
        None
114
    }
115
}
116
117
impl std::fmt::Display for ServerError {
118
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119
        write!(formatter, "{} ({})", self.message, self.errcode)
120
    }
121
}
122
123
#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
124
pub(crate) fn classify(source: &(dyn std::error::Error + 'static)) -> ErrorKind {
125
    let mut current = Some(source);
126
    while let Some(error) = current {
127
        if let Some(error) = error.downcast_ref::<matrix_sdk::Error>() {
128
            return classify_sdk_error(error);
129
        }
130
        if let Some(error) = error.downcast_ref::<HttpError>() {
131
            return classify_http_error(error);
132
        }
133
        if let Some(error) = error.downcast_ref::<OAuthError>() {
134
            return classify_oauth_error(error);
135
        }
136
        if let Some(error) = error.downcast_ref::<RefreshTokenError>() {
137
            return classify_refresh_token_error(error);
138
        }
139
        current = error.source();
140
    }
141
    ErrorKind::Other
142
}
143
144
impl Error {
145
    pub(crate) fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
146
        Self {
147
            kind,
148
            message: message.into(),
149
            challenge: None,
150
            server: None,
151
            source: None,
152
        }
153
    }
154
155
    pub(crate) fn with_source(
156
        kind: ErrorKind,
157
        message: impl Into<String>,
158
        source: impl std::error::Error + SendOutsideWasm + SyncOutsideWasm + 'static,
159
    ) -> Self {
160
        Self {
161
            server: ServerError::find(&source),
162
            source: Some(Arc::new(source)),
163
            ..Self::new(kind, message)
164
        }
165
    }
166
167
    pub(crate) fn interrupted() -> Self {
168
        Self::new(
169
            ErrorKind::Interrupted,
170
            "the engine stopped before the operation finished",
171
        )
172
    }
173
174
    pub(crate) fn room_unknown() -> Self {
175
        Self::new(
176
            ErrorKind::RoomUnknown,
177
            "room is not available in the local store",
178
        )
179
    }
180
181
    pub(crate) fn interactive_auth(challenge: InteractiveAuthChallenge) -> Self {
182
        Self {
183
            challenge: Some(Box::new(challenge)),
184
            ..Self::new(
185
                ErrorKind::InteractiveAuthRequired,
186
                "interactive authentication is required",
187
            )
188
        }
189
    }
190
191
    pub(crate) fn from_sdk(message: impl Into<String>, source: matrix_sdk::Error) -> Self {
192
        Self::with_source(classify_sdk_error(&source), message, source)
193
    }
194
195
    pub(crate) fn from_http(message: impl Into<String>, source: HttpError) -> Self {
196
        Self::with_source(classify_http_error(&source), message, source)
197
    }
198
199
    pub(crate) fn from_refresh_token(
200
        message: impl Into<String>,
201
        source: RefreshTokenError,
202
    ) -> Self {
203
        Self::with_source(classify_refresh_token_error(&source), message, source)
204
    }
205
206
    pub(crate) fn from_recovery(message: impl Into<String>, source: RecoveryError) -> Self {
207
        let kind = match &source {
208
            RecoveryError::Sdk(source) => classify_sdk_error(source),
209
            RecoveryError::SecretStorage(SecretStorageError::Sdk(source)) => {
210
                classify_sdk_error(source)
211
            }
212
            RecoveryError::SecretStorage(SecretStorageError::ImportError { error, .. }) => {
213
                match error {
214
                    ImportError::Sdk(source) => classify_sdk_error(source),
215
                    _ => ErrorKind::RecoveryRejected,
216
                }
217
            }
218
            RecoveryError::SecretStorage(SecretStorageError::Verification(source)) => {
219
                match source {
220
                    ManualVerifyError::Http(source) => classify_http_error(source),
221
                    _ => ErrorKind::RecoveryRejected,
222
                }
223
            }
224
            RecoveryError::SecretStorage(SecretStorageError::SecretStorageKey(_)) => {
225
                ErrorKind::RecoveryRejected
226
            }
227
            RecoveryError::SecretStorage(SecretStorageError::Storage(_)) => ErrorKind::Store,
228
            RecoveryError::BackupExistsOnServer => ErrorKind::KeyBackupExists,
229
            RecoveryError::SecretStorage(_) => ErrorKind::RecoveryRejected,
230
        };
231
        Self::with_source(kind, message, source)
232
    }
233
234
    pub(crate) fn from_verification_request(
235
        message: impl Into<String>,
236
        source: RequestVerificationError,
237
    ) -> Self {
238
        let kind = match &source {
239
            RequestVerificationError::Sdk(source) => classify_sdk_error(source),
240
            RequestVerificationError::RoomCreation(_) => ErrorKind::Encryption,
241
        };
242
        Self::with_source(kind, message, source)
243
    }
244
}
245
246
impl PartialEq for Error {
247
    fn eq(&self, other: &Self) -> bool {
248
        self.kind == other.kind
249
            && self.message == other.message
250
            && self.challenge == other.challenge
251
            && self.server == other.server
252
    }
253
}
254
255
pub(crate) fn classify_sdk_error(source: &matrix_sdk::Error) -> ErrorKind {
256
    match source {
257
        matrix_sdk::Error::Http(source) => classify_http_error(source),
258
        matrix_sdk::Error::OAuth(source) => classify_oauth_error(source),
259
        matrix_sdk::Error::AuthenticationRequired => ErrorKind::Unauthenticated,
260
        matrix_sdk::Error::BadCryptoStoreState
261
        | matrix_sdk::Error::CryptoStoreError(_)
262
        | matrix_sdk::Error::CrossProcessLockError(_)
263
        | matrix_sdk::Error::StateStore(_)
264
        | matrix_sdk::Error::EventCacheStore(_)
265
        | matrix_sdk::Error::MediaStore(_) => ErrorKind::Store,
266
        matrix_sdk::Error::NoOlmMachine
267
        | matrix_sdk::Error::OlmError(_)
268
        | matrix_sdk::Error::MegolmError(_)
269
        | matrix_sdk::Error::DecryptorError(_) => ErrorKind::Encryption,
270
        matrix_sdk::Error::Media(_) => ErrorKind::MediaUnavailable,
271
        matrix_sdk::Error::Timeout => ErrorKind::Network,
272
        _ => ErrorKind::Other,
273
    }
274
}
275
276
fn classify_http_error(source: &HttpError) -> ErrorKind {
277
    match source {
278
        HttpError::Reqwest(_) => ErrorKind::Network,
279
        HttpError::Api(_)
280
            if source
281
                .as_client_api_error()
282
                .is_some_and(|api| api.status_code.as_u16() == 429) =>
283
        {
284
            ErrorKind::RateLimited
285
        }
286
        HttpError::Api(_) => ErrorKind::Server,
287
        HttpError::RefreshToken(source) => classify_refresh_token_error(source),
288
        HttpError::Cached(source) => classify_http_error(source),
289
        _ => ErrorKind::Other,
290
    }
291
}
292
293
fn classify_refresh_token_error(source: &RefreshTokenError) -> ErrorKind {
294
    match source {
295
        RefreshTokenError::RefreshTokenRequired => ErrorKind::Unauthenticated,
296
        RefreshTokenError::MatrixAuth(source) => classify_http_error(source),
297
        RefreshTokenError::OAuth(source) => classify_oauth_error(source),
298
    }
299
}
300
301
fn classify_oauth_error(source: &OAuthError) -> ErrorKind {
302
    let classify_token_request_error = |source: &_| match source {
303
        RequestTokenError::Request(_) => ErrorKind::Network,
304
        RequestTokenError::ServerResponse(_) => ErrorKind::Unauthenticated,
305
        _ => ErrorKind::Other,
306
    };
307
    match source {
308
        OAuthError::Discovery(source) => match source {
309
            OAuthDiscoveryError::Http(source) => classify_http_error(source),
310
            OAuthDiscoveryError::Oidc(source) => classify_token_request_error(source),
311
            _ => ErrorKind::Unauthenticated,
312
        },
313
        OAuthError::RefreshToken(source) => classify_token_request_error(source),
314
        OAuthError::AuthorizationCode(OAuthAuthorizationCodeError::RequestToken(source)) => {
315
            classify_token_request_error(source)
316
        }
317
        OAuthError::AuthorizationCode(_) => ErrorKind::Unauthenticated,
318
        OAuthError::Logout(source) => match source {
319
            OAuthTokenRevocationError::Revoke(RequestTokenError::Request(_)) => ErrorKind::Network,
320
            OAuthTokenRevocationError::Revoke(RequestTokenError::ServerResponse(_)) => {
321
                ErrorKind::Unauthenticated
322
            }
323
            _ => ErrorKind::Other,
324
        },
325
        _ => ErrorKind::Unauthenticated,
326
    }
327
}