atagen

macltok

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