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
60impl From<rmp_serde::decode::Error> for Error {
61    fn from(e: rmp_serde::decode::Error) -> Self {
62        Self::Serialization(e.to_string())
63    }
64}
65
66impl From<rmp_serde::encode::Error> for Error {
67    fn from(e: rmp_serde::encode::Error) -> Self {
68        Self::Serialization(e.to_string())
69    }
70}
71
72#[extend::ext(name=RusqliteResultExt)]
73pub impl<T> Result<T, rusqlite::Error> {
74    // Convert an rusqlite::Error to our error type, with a context value
75    fn with_context(self, context: &str) -> Result<T, Error> {
76        self.map_err(|e| Error::sql(e, context))
77    }
78}
79
80/// The error type for all Suggest component operations. These errors are
81/// exposed to your application, which should handle them as needed.
82#[derive(Debug, thiserror::Error, uniffi::Error)]
83#[non_exhaustive]
84pub enum SuggestApiError {
85    #[error("Network error: {reason}")]
86    Network { reason: String },
87    /// The server requested a backoff after too many requests
88    #[error("Backoff")]
89    Backoff { seconds: u64 },
90    /// An operation was interrupted by calling `SuggestStore.interrupt()`
91    #[error("Interrupted")]
92    Interrupted,
93    #[error("Other error: {reason}")]
94    Other { reason: String },
95}
96
97// Define how our internal errors are handled and converted to external errors
98// See `support/error/README.md` for how this works, especially the warning about PII.
99impl GetErrorHandling for Error {
100    type ExternalError = SuggestApiError;
101
102    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
103        match self {
104            // Do nothing for interrupted errors, this is just normal operation.
105            Self::Interrupted(_) => ErrorHandling::convert(SuggestApiError::Interrupted),
106            // Network errors are expected to happen in practice.  Let's log, but not report them.
107            Self::RemoteSettings(RemoteSettingsError::Network { reason }) => {
108                ErrorHandling::convert(SuggestApiError::Network {
109                    reason: reason.clone(),
110                })
111                .log_warning()
112            }
113            // Backoff error shouldn't happen in practice, so let's report them for now.
114            // If these do happen in practice and we decide that there is a valid reason for them,
115            // then consider switching from reporting to Sentry to counting in Glean.
116            Self::RemoteSettings(RemoteSettingsError::Backoff { seconds }) => {
117                ErrorHandling::convert(SuggestApiError::Backoff { seconds: *seconds })
118                    .report_error("suggest-backoff")
119            }
120            _ => ErrorHandling::convert(SuggestApiError::Other {
121                reason: self.to_string(),
122            })
123            .report_error("suggest-unexpected"),
124        }
125    }
126}