atagen

macltok

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