suggest/
error.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 */
5
6use error_support::{ErrorHandling, GetErrorHandling};
7use remote_settings::RemoteSettingsError;
8
9/// A list of errors that are internal to the component. This is the error
10/// type for private and crate-internal methods, and is never returned to the
11/// application.
12#[derive(Debug, thiserror::Error)]
13pub enum Error {
14    #[error("Error opening database: {0}")]
15    OpenDatabase(#[from] sql_support::open_database::Error),
16
17    #[error("Error executing SQL: {inner} (context: {context})")]
18    Sql {
19        inner: rusqlite::Error,
20        context: String,
21    },
22
23    #[error("Serialization error: {0}")]
24    Serialization(String),
25
26    #[error("Error from Remote Settings: {0}")]
27    RemoteSettings(#[from] RemoteSettingsError),
28
29    #[error("Remote settings record is missing an attachment (id: u64)")]
30    MissingAttachment(String),
31
32    #[error("Operation interrupted")]
33    Interrupted(#[from] interrupt_support::Interrupted),
34
35    #[error("SuggestStoreBuilder {0}")]
36    SuggestStoreBuilder(String),
37}
38
39impl Error {
40    fn sql(e: rusqlite::Error, context: impl Into<String>) -> Self {
41        Self::Sql {
42            inner: e,
43            context: context.into(),
44        }
45    }
46}
47
48impl From<rusqlite::Error> for Error {
49    fn from(e: rusqlite::Error) -> Self {
50        Self::sql(e, "<none>")
51    }
52}
53
54impl From<serde_json::Error> for Error {
55    fn from(e: serde_json::Error) -> Self {
56        Self::Serialization(e.to_string())
57    }
58}
59
60#[extend::ext(name=RusqliteResultExt)]
61pub impl<T> Result<T, rusqlite::Error> {
62    // Convert an rusqlite::Error to our error type, with a context value
63    fn with_context(self, context: &str) -> Result<T, Error> {
64        self.map_err(|e| Error::sql(e, context))
65    }
66}
67
68/// The error type for all Suggest component operations. These errors are
69/// exposed to your application, which should handle them as needed.
70#[derive(Debug, thiserror::Error, uniffi::Error)]
71#[non_exhaustive]
72pub enum SuggestApiError {
73    #[error("Network error: {reason}")]
74    Network { reason: String },
75    /// The server requested a backoff after too many requests
76    #[error("Backoff")]
77    Backoff { seconds: u64 },
78    /// An operation was interrupted by calling `SuggestStore.interrupt()`
79    #[error("Interrupted")]
80    Interrupted,
81    #[error("Other error: {reason}")]
82    Other { reason: String },
83}
84
85// Define how our internal errors are handled and converted to external errors
86// See `support/error/README.md` for how this works, especially the warning about PII.
87impl GetErrorHandling for Error {
88    type ExternalError = SuggestApiError;
89
90    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
91        match self {
92            // Do nothing for interrupted errors, this is just normal operation.
93            Self::Interrupted(_) => ErrorHandling::convert(SuggestApiError::Interrupted),
94            // Network errors are expected to happen in practice.  Let's log, but not report them.
95            Self::RemoteSettings(RemoteSettingsError::Network { reason }) => {
96                ErrorHandling::convert(SuggestApiError::Network {
97                    reason: reason.clone(),
98                })
99                .log_warning()
100            }
101            // Backoff error shouldn't happen in practice, so let's report them for now.
102            // If these do happen in practice and we decide that there is a valid reason for them,
103            // then consider switching from reporting to Sentry to counting in Glean.
104            Self::RemoteSettings(RemoteSettingsError::Backoff { seconds }) => {
105                ErrorHandling::convert(SuggestApiError::Backoff { seconds: *seconds })
106                    .report_error("suggest-backoff")
107            }
108            _ => ErrorHandling::convert(SuggestApiError::Other {
109                reason: self.to_string(),
110            })
111            .report_error("suggest-unexpected"),
112        }
113    }
114}