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
84// Define how our internal errors are handled and converted to external errors
85// See `support/error/README.md` for how this works, especially the warning about PII.
86impl GetErrorHandling for Error {
87    type ExternalError = AutofillApiError;
88
89    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
90        match self {
91            Self::OpenDatabaseError(e) => ErrorHandling::convert(AutofillApiError::SqlError {
92                reason: e.to_string(),
93            })
94            .report_error("autofill-open-database-error"),
95
96            Self::SqlError(e) => ErrorHandling::convert(AutofillApiError::SqlError {
97                reason: e.to_string(),
98            })
99            .report_error("autofill-sql-error"),
100
101            Self::IoError(e) => {
102                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
103                    reason: e.to_string(),
104                })
105                .report_error("autofill-io-error")
106            }
107
108            Self::InterruptedError(_) => ErrorHandling::convert(AutofillApiError::InterruptedError),
109
110            Self::IllegalDatabasePath(path) => ErrorHandling::convert(AutofillApiError::SqlError {
111                reason: format!("Path not found: {}", path.to_string_lossy()),
112            })
113            .report_error("autofill-illegal-database-path"),
114
115            Self::JsonError(e) => {
116                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
117                    reason: e.to_string(),
118                })
119                .report_error("autofill-json-error")
120            }
121
122            Self::InvalidSyncPayload(reason) => {
123                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
124                    reason: reason.clone(),
125                })
126                .report_error("autofill-invalid-sync-payload")
127            }
128
129            Self::CryptoError(e) => ErrorHandling::convert(AutofillApiError::CryptoError {
130                reason: e.to_string(),
131            })
132            .report_error("autofill-crypto-error"),
133
134            Self::MissingEncryptionKey => ErrorHandling::convert(AutofillApiError::CryptoError {
135                reason: "Missing encryption key".to_string(),
136            })
137            .report_error("autofill-missing-encryption-key"),
138
139            Self::NoSuchRecord(guid) => {
140                ErrorHandling::convert(AutofillApiError::NoSuchRecord { guid: guid.clone() })
141                    .log_warning()
142            }
143
144            Self::DatabaseClosed => {
145                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
146                    reason: "The store is closed".to_string(),
147                })
148                .report_error("autofill-database-closed")
149            }
150        }
151    }
152}