autofill/
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 interrupt_support::Interrupted;
8
9/// Result enum for the public API
10pub type ApiResult<T> = std::result::Result<T, AutofillApiError>;
11
12/// Result enum for internal functions
13pub type Result<T> = std::result::Result<T, Error>;
14
15// Errors we return via the public interface.
16#[derive(Debug, thiserror::Error)]
17pub enum AutofillApiError {
18    #[error("Error executing SQL: {reason}")]
19    SqlError { reason: String },
20
21    #[error("Operation interrupted")]
22    InterruptedError,
23
24    #[error("Crypto Error: {reason}")]
25    CryptoError { reason: String },
26
27    #[error("No record with guid exists: {guid}")]
28    NoSuchRecord { guid: String },
29
30    #[error("Unexpected Error: {reason}")]
31    UnexpectedAutofillApiError { reason: String },
32}
33
34// The `sync15` BridgedEngine traits use `anyhow::Result`, so the bridged engine
35// in `sync::bridge` needs those errors mapped onto the public error type before
36// UniFFI can expose its methods.
37impl From<anyhow::Error> for AutofillApiError {
38    fn from(value: anyhow::Error) -> Self {
39        AutofillApiError::UnexpectedAutofillApiError {
40            reason: value.to_string(),
41        }
42    }
43}
44
45#[derive(Debug, thiserror::Error)]
46pub enum Error {
47    #[error("Error opening database: {0}")]
48    OpenDatabaseError(#[from] sql_support::open_database::Error),
49
50    #[error("Error executing SQL: {0}")]
51    SqlError(#[from] rusqlite::Error),
52
53    #[error("IO error: {0}")]
54    IoError(#[from] std::io::Error),
55
56    #[error("Operation interrupted")]
57    InterruptedError(#[from] Interrupted),
58
59    // This will happen if you provide something absurd like
60    // "/" or "" as your database path. For more subtley broken paths,
61    // we'll likely return an IoError.
62    #[error("Illegal database path: {0:?}")]
63    IllegalDatabasePath(std::path::PathBuf),
64
65    #[error("JSON Error: {0}")]
66    JsonError(#[from] serde_json::Error),
67
68    #[error("Invalid sync payload: {0}")]
69    InvalidSyncPayload(String),
70
71    #[error("Crypto Error: {0}")]
72    CryptoError(#[from] jwcrypto::JwCryptoError),
73
74    #[error("Missing local encryption key")]
75    MissingEncryptionKey,
76
77    #[error("No record with guid exists: {0}")]
78    NoSuchRecord(String),
79
80    #[error("The store is closed")]
81    DatabaseClosed,
82}
83
84impl From<sql_support::path::Error> for Error {
85    fn from(e: sql_support::path::Error) -> Self {
86        match e {
87            sql_support::path::Error::IllegalDatabasePath(path) => Error::IllegalDatabasePath(path),
88            sql_support::path::Error::IoError(e) => Error::IoError(e),
89        }
90    }
91}
92
93// Define how our internal errors are handled and converted to external errors
94// See `support/error/README.md` for how this works, especially the warning about PII.
95impl GetErrorHandling for Error {
96    type ExternalError = AutofillApiError;
97
98    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
99        match self {
100            Self::OpenDatabaseError(e) => ErrorHandling::convert(AutofillApiError::SqlError {
101                reason: e.to_string(),
102            })
103            .report_error("autofill-open-database-error"),
104
105            Self::SqlError(e) => ErrorHandling::convert(AutofillApiError::SqlError {
106                reason: e.to_string(),
107            })
108            .report_error("autofill-sql-error"),
109
110            Self::IoError(e) => {
111                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
112                    reason: e.to_string(),
113                })
114                .report_error("autofill-io-error")
115            }
116
117            Self::InterruptedError(_) => ErrorHandling::convert(AutofillApiError::InterruptedError),
118
119            Self::IllegalDatabasePath(path) => ErrorHandling::convert(AutofillApiError::SqlError {
120                reason: format!("Path not found: {}", path.to_string_lossy()),
121            })
122            .report_error("autofill-illegal-database-path"),
123
124            Self::JsonError(e) => {
125                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
126                    reason: e.to_string(),
127                })
128                .report_error("autofill-json-error")
129            }
130
131            Self::InvalidSyncPayload(reason) => {
132                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
133                    reason: reason.clone(),
134                })
135                .report_error("autofill-invalid-sync-payload")
136            }
137
138            Self::CryptoError(e) => ErrorHandling::convert(AutofillApiError::CryptoError {
139                reason: e.to_string(),
140            })
141            .report_error("autofill-crypto-error"),
142
143            Self::MissingEncryptionKey => ErrorHandling::convert(AutofillApiError::CryptoError {
144                reason: "Missing encryption key".to_string(),
145            })
146            .report_error("autofill-missing-encryption-key"),
147
148            Self::NoSuchRecord(guid) => {
149                ErrorHandling::convert(AutofillApiError::NoSuchRecord { guid: guid.clone() })
150                    .log_warning()
151            }
152
153            Self::DatabaseClosed => {
154                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
155                    reason: "The store is closed".to_string(),
156                })
157                .report_error("autofill-database-closed")
158            }
159        }
160    }
161}