atagen

macltok

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