atagen

macltok

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