fractal/
user_facing_error.rs

1use std::time::{Duration, SystemTime};
2
3use gettextrs::gettext;
4use matrix_sdk::{ClientBuildError, Error, HttpError};
5use ruma::api::client::error::{ErrorBody, ErrorKind, RetryAfter};
6
7use crate::ngettext_f;
8
9pub trait UserFacingError {
10    fn to_user_facing(&self) -> String;
11}
12
13impl UserFacingError for HttpError {
14    fn to_user_facing(&self) -> String {
15        if let HttpError::Reqwest(error) = self {
16            // TODO: Add more information based on the error
17            if error.is_timeout() {
18                gettext("Connection timed out.")
19            } else {
20                gettext("Could not connect to the homeserver.")
21            }
22        } else if let Some(ErrorBody::Standard { kind, message }) =
23            self.as_client_api_error().map(|error| &error.body)
24        {
25            match kind {
26                ErrorKind::Forbidden { .. } => gettext("Invalid credentials."),
27                ErrorKind::UserDeactivated => gettext("Account deactivated."),
28                ErrorKind::LimitExceeded { retry_after } => {
29                    if let Some(retry_after) = retry_after {
30                        let duration = match retry_after {
31                            RetryAfter::Delay(duration) => *duration,
32                            RetryAfter::DateTime(until) => until
33                                .duration_since(SystemTime::now())
34                                // An error means that the date provided is in the past, which
35                                // doesn't make sense. Let's not panic anyway and default to 1
36                                // second.
37                                .unwrap_or_else(|_| Duration::from_secs(1)),
38                        };
39                        let secs = duration.as_secs() as u32;
40                        ngettext_f(
41                            // Translators: Do NOT translate the content between '{' and '}',
42                            // this is a variable name.
43                            "Rate limit exceeded, retry in 1 second.",
44                            "Rate limit exceeded, retry in {n} seconds.",
45                            secs,
46                            &[("n", &secs.to_string())],
47                        )
48                    } else {
49                        gettext("Rate limit exceeded, try again later.")
50                    }
51                }
52                _ => {
53                    // TODO: The server may not give us pretty enough error message. We should
54                    // add our own error message.
55                    message.clone()
56                }
57            }
58        } else {
59            gettext("Unexpected connection error.")
60        }
61    }
62}
63
64impl UserFacingError for Error {
65    fn to_user_facing(&self) -> String {
66        match self {
67            Error::DecryptorError(_) => gettext("Could not decrypt the event."),
68            Error::Http(http_error) => http_error.to_user_facing(),
69            _ => gettext("Unexpected error."),
70        }
71    }
72}
73
74impl UserFacingError for ClientBuildError {
75    fn to_user_facing(&self) -> String {
76        match self {
77            ClientBuildError::Url(_) => gettext("Invalid URL."),
78            ClientBuildError::AutoDiscovery(_) => gettext("Could not discover homeserver."),
79            ClientBuildError::Http(err) => err.to_user_facing(),
80            ClientBuildError::SqliteStore(_) => gettext("Could not open the store."),
81            _ => gettext("Unexpected error."),
82        }
83    }
84}