atagen

macltok

public
Star0
Watch0
7c26fad34822af1c23fc900f448c86c2cb4d10bd
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/error.rsRust245 lines8.5 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
    #[source]
37
    #[cfg_attr(feature = "serde", serde(skip))]
38
    source: Option<Arc<SourceError>>,
39
}
40
41
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43
#[non_exhaustive]
44
pub enum ErrorKind {
45
    /// the engine stopped, or the task doing the work died, before answering
46
    Interrupted,
47
    Unauthenticated,
48
    InteractiveAuthRequired,
49
    Invalid,
50
    RoomUnknown,
51
    RoomNotOpen,
52
    TimelineNotOpen,
53
    RoomBusy,
54
    QueueFull,
55
    EmptyBody,
56
    MediaUnknown,
57
    MediaTooLarge,
58
    MediaUnavailable,
59
    VerificationInactive,
60
    VerificationUnsupported,
61
    NoCrossSigningIdentity,
62
    RecoveryRejected,
63
    Network,
64
    Server,
65
    Store,
66
    Timeline,
67
    Encryption,
68
    Other,
69
}
70
71
impl Error {
72
    pub(crate) fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
73
        Self {
74
            kind,
75
            message: message.into(),
76
            challenge: None,
77
            source: None,
78
        }
79
    }
80
81
    pub(crate) fn with_source(
82
        kind: ErrorKind,
83
        message: impl Into<String>,
84
        source: impl std::error::Error + SendOutsideWasm + SyncOutsideWasm + 'static,
85
    ) -> Self {
86
        Self {
87
            source: Some(Arc::new(source)),
88
            ..Self::new(kind, message)
89
        }
90
    }
91
92
    pub(crate) fn interrupted() -> Self {
93
        Self::new(
94
            ErrorKind::Interrupted,
95
            "the engine stopped before the operation finished",
96
        )
97
    }
98
99
    pub(crate) fn room_unknown() -> Self {
100
        Self::new(
101
            ErrorKind::RoomUnknown,
102
            "room is not available in the local store",
103
        )
104
    }
105
106
    pub(crate) fn interactive_auth(challenge: InteractiveAuthChallenge) -> Self {
107
        Self {
108
            challenge: Some(Box::new(challenge)),
109
            ..Self::new(
110
                ErrorKind::InteractiveAuthRequired,
111
                "interactive authentication is required",
112
            )
113
        }
114
    }
115
116
    pub(crate) fn from_sdk(message: impl Into<String>, source: matrix_sdk::Error) -> Self {
117
        Self::with_source(classify_sdk_error(&source), message, source)
118
    }
119
120
    pub(crate) fn from_http(message: impl Into<String>, source: HttpError) -> Self {
121
        Self::with_source(classify_http_error(&source), message, source)
122
    }
123
124
    pub(crate) fn from_refresh_token(
125
        message: impl Into<String>,
126
        source: RefreshTokenError,
127
    ) -> Self {
128
        Self::with_source(classify_refresh_token_error(&source), message, source)
129
    }
130
131
    pub(crate) fn from_recovery(message: impl Into<String>, source: RecoveryError) -> Self {
132
        let kind = match &source {
133
            RecoveryError::Sdk(source) => classify_sdk_error(source),
134
            RecoveryError::SecretStorage(SecretStorageError::Sdk(source)) => {
135
                classify_sdk_error(source)
136
            }
137
            RecoveryError::SecretStorage(SecretStorageError::ImportError { error, .. }) => {
138
                match error {
139
                    ImportError::Sdk(source) => classify_sdk_error(source),
140
                    _ => ErrorKind::RecoveryRejected,
141
                }
142
            }
143
            RecoveryError::SecretStorage(SecretStorageError::Verification(source)) => {
144
                match source {
145
                    ManualVerifyError::Http(source) => classify_http_error(source),
146
                    _ => ErrorKind::RecoveryRejected,
147
                }
148
            }
149
            RecoveryError::SecretStorage(SecretStorageError::SecretStorageKey(_)) => {
150
                ErrorKind::RecoveryRejected
151
            }
152
            RecoveryError::SecretStorage(SecretStorageError::Storage(_)) => ErrorKind::Store,
153
            RecoveryError::BackupExistsOnServer | RecoveryError::SecretStorage(_) => {
154
                ErrorKind::RecoveryRejected
155
            }
156
        };
157
        Self::with_source(kind, message, source)
158
    }
159
160
    pub(crate) fn from_verification_request(
161
        message: impl Into<String>,
162
        source: RequestVerificationError,
163
    ) -> Self {
164
        let kind = match &source {
165
            RequestVerificationError::Sdk(source) => classify_sdk_error(source),
166
            RequestVerificationError::RoomCreation(_) => ErrorKind::Encryption,
167
        };
168
        Self::with_source(kind, message, source)
169
    }
170
}
171
172
impl PartialEq for Error {
173
    fn eq(&self, other: &Self) -> bool {
174
        self.kind == other.kind
175
            && self.message == other.message
176
            && self.challenge == other.challenge
177
    }
178
}
179
180
pub(crate) fn classify_sdk_error(source: &matrix_sdk::Error) -> ErrorKind {
181
    match source {
182
        matrix_sdk::Error::Http(source) => classify_http_error(source),
183
        matrix_sdk::Error::OAuth(source) => classify_oauth_error(source),
184
        matrix_sdk::Error::AuthenticationRequired => ErrorKind::Unauthenticated,
185
        matrix_sdk::Error::BadCryptoStoreState
186
        | matrix_sdk::Error::CryptoStoreError(_)
187
        | matrix_sdk::Error::CrossProcessLockError(_)
188
        | matrix_sdk::Error::StateStore(_)
189
        | matrix_sdk::Error::EventCacheStore(_)
190
        | matrix_sdk::Error::MediaStore(_) => ErrorKind::Store,
191
        matrix_sdk::Error::NoOlmMachine
192
        | matrix_sdk::Error::OlmError(_)
193
        | matrix_sdk::Error::MegolmError(_)
194
        | matrix_sdk::Error::DecryptorError(_) => ErrorKind::Encryption,
195
        matrix_sdk::Error::Media(_) => ErrorKind::MediaUnavailable,
196
        matrix_sdk::Error::Timeout => ErrorKind::Network,
197
        _ => ErrorKind::Other,
198
    }
199
}
200
201
fn classify_http_error(source: &HttpError) -> ErrorKind {
202
    match source {
203
        HttpError::Reqwest(_) => ErrorKind::Network,
204
        HttpError::Api(_) => ErrorKind::Server,
205
        HttpError::RefreshToken(source) => classify_refresh_token_error(source),
206
        HttpError::Cached(source) => classify_http_error(source),
207
        _ => ErrorKind::Other,
208
    }
209
}
210
211
fn classify_refresh_token_error(source: &RefreshTokenError) -> ErrorKind {
212
    match source {
213
        RefreshTokenError::RefreshTokenRequired => ErrorKind::Unauthenticated,
214
        RefreshTokenError::MatrixAuth(source) => classify_http_error(source),
215
        RefreshTokenError::OAuth(source) => classify_oauth_error(source),
216
    }
217
}
218
219
fn classify_oauth_error(source: &OAuthError) -> ErrorKind {
220
    let classify_token_request_error = |source: &_| match source {
221
        RequestTokenError::Request(_) => ErrorKind::Network,
222
        RequestTokenError::ServerResponse(_) => ErrorKind::Unauthenticated,
223
        _ => ErrorKind::Other,
224
    };
225
    match source {
226
        OAuthError::Discovery(source) => match source {
227
            OAuthDiscoveryError::Http(source) => classify_http_error(source),
228
            OAuthDiscoveryError::Oidc(source) => classify_token_request_error(source),
229
            _ => ErrorKind::Unauthenticated,
230
        },
231
        OAuthError::RefreshToken(source) => classify_token_request_error(source),
232
        OAuthError::AuthorizationCode(OAuthAuthorizationCodeError::RequestToken(source)) => {
233
            classify_token_request_error(source)
234
        }
235
        OAuthError::AuthorizationCode(_) => ErrorKind::Unauthenticated,
236
        OAuthError::Logout(source) => match source {
237
            OAuthTokenRevocationError::Revoke(RequestTokenError::Request(_)) => ErrorKind::Network,
238
            OAuthTokenRevocationError::Revoke(RequestTokenError::ServerResponse(_)) => {
239
                ErrorKind::Unauthenticated
240
            }
241
            _ => ErrorKind::Other,
242
        },
243
        _ => ErrorKind::Unauthenticated,
244
    }
245
}