atagen

macltok

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