atagen

macltok

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