atagen

macltok

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