nimbus/
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
6//! Not complete yet
7//! This is where the error definitions can go
8//! TODO: Implement proper error handling, this would include defining the error enum,
9//! impl std::error::Error using `thiserror` and ensuring all errors are handled appropriately
10
11use std::borrow::Cow;
12use std::num::{ParseIntError, TryFromIntError};
13
14// reexport logging helpers.
15pub use error_support::{debug, error, info, trace, warn};
16#[cfg(feature = "stateful")]
17use firefox_versioning::error::VersionParsingError;
18
19#[derive(Debug, thiserror::Error)]
20pub enum NimbusError {
21    #[error("Initialization of the database is not yet complete")]
22    DatabaseNotReady,
23
24    #[error("Empty ratios!")]
25    EmptyRatiosError,
26
27    #[error("EvaluationError: {0}")]
28    EvaluationError(String),
29
30    #[error("IO error: {0}")]
31    IOError(#[from] std::io::Error),
32
33    #[error("Internal error: {0}")]
34    InternalError(&'static str),
35
36    #[error("Invalid experiment data received")]
37    InvalidExperimentFormat,
38
39    #[error("Invalid Expression - didn't evaluate to a bool")]
40    InvalidExpression,
41
42    #[error("InvalidFractionError: Should be between 0 and 1")]
43    InvalidFraction,
44
45    #[error("Invalid path: {0}")]
46    InvalidPath(String),
47
48    #[error("Invalid persisted data")]
49    InvalidPersistedData,
50
51    #[error("JSON Error: {0} — {1}")]
52    JSONError(String, String),
53
54    #[error("The branch {0} does not exist for the experiment {1}")]
55    NoSuchBranch(String, String),
56
57    #[error("The experiment {0} does not exist")]
58    NoSuchExperiment(String),
59
60    #[error("Attempt to access an element that is out of bounds")]
61    OutOfBoundsError,
62
63    #[error("ParseIntError: {0}")]
64    ParseIntError(#[from] ParseIntError),
65
66    #[error("Transform parameter error: {0}")]
67    TransformParameterError(String),
68
69    #[error("TryFromIntError: {0}")]
70    TryFromIntError(#[from] TryFromIntError),
71
72    #[error("TryInto error: {0}")]
73    TryFromSliceError(#[from] std::array::TryFromSliceError),
74
75    #[error("UniFFI callback error: {0}")]
76    UniFFICallbackError(#[from] uniffi::UnexpectedUniFFICallbackError),
77
78    #[error("Error parsing URL: {0}")]
79    UrlParsingError(#[from] url::ParseError),
80
81    #[error("UUID parsing error: {0}")]
82    UuidError(#[from] uuid::Error),
83
84    #[error("Error parsing a string into a version {0}")]
85    VersionParsingError(String),
86
87    // Stateful errors.
88    #[cfg(feature = "stateful")]
89    #[error("Behavior error: {0}")]
90    BehaviorError(#[from] BehaviorError),
91
92    #[cfg(feature = "stateful")]
93    #[error("Error with Remote Settings client: {0}")]
94    ClientError(#[from] remote_settings::RemoteSettingsError),
95
96    #[cfg(feature = "stateful")]
97    #[error("Regex error: {0}")]
98    RegexError(#[from] regex::Error),
99
100    #[cfg(feature = "stateful")]
101    #[error("Rkv error: {0}")]
102    RkvError(#[from] rkv::StoreError),
103
104    // Cirrus-only errors.
105    #[cfg(not(feature = "stateful"))]
106    #[error("Error in Cirrus: {0}")]
107    CirrusError(#[from] CirrusClientError),
108}
109
110#[cfg(feature = "stateful")]
111#[derive(Debug, thiserror::Error)]
112pub enum BehaviorError {
113    #[error(r#"EventQueryParseError: "{0}" is not a valid EventQuery"#)]
114    EventQueryParseError(String),
115
116    #[error("EventQueryTypeParseError: {0} is not a valid EventQueryType")]
117    EventQueryTypeParseError(String),
118
119    #[error("IntervalParseError: {0} is not a valid Interval")]
120    IntervalParseError(String),
121
122    #[error("Invalid duration: {0}")]
123    InvalidDuration(String),
124
125    #[error("Invalid state: {0}")]
126    InvalidState(String),
127
128    #[error("The event store is not available on the targeting attributes")]
129    MissingEventStore,
130
131    #[error("The recorded context is not available on the nimbus client")]
132    MissingRecordedContext,
133
134    #[error(r#"TypeError: "{0}" is not of type {1}"#)]
135    TypeError(String, String),
136}
137
138#[cfg(not(feature = "stateful"))]
139#[derive(Debug, thiserror::Error)]
140pub enum CirrusClientError {
141    #[error("Request missing parameter: {0}")]
142    RequestMissingParameter(String),
143}
144
145#[cfg(test)]
146impl From<serde_json::Error> for NimbusError {
147    fn from(error: serde_json::Error) -> Self {
148        NimbusError::JSONError("test".into(), error.to_string())
149    }
150}
151
152impl<'a> From<jexl_eval::error::EvaluationError<'a>> for NimbusError {
153    fn from(eval_error: jexl_eval::error::EvaluationError<'a>) -> Self {
154        NimbusError::EvaluationError(eval_error.to_string())
155    }
156}
157
158#[cfg(feature = "stateful")]
159impl From<VersionParsingError> for NimbusError {
160    fn from(eval_error: VersionParsingError) -> Self {
161        NimbusError::VersionParsingError(eval_error.to_string())
162    }
163}
164
165pub type Result<T, E = NimbusError> = std::result::Result<T, E>;
166
167/// An Error extension trait that allows simplified error codes to be submitted
168/// in telemetry.
169pub trait ErrorCode: std::error::Error {
170    /// Return the error code for the given error.
171    fn error_code(&self) -> Cow<'static, str>;
172}
173
174#[cfg(feature = "stateful")]
175impl ErrorCode for NimbusError {
176    fn error_code(&self) -> Cow<'static, str> {
177        match self {
178            Self::BehaviorError(e) => format!("BehaviorError({})", e.error_code()).into(),
179            Self::ClientError(..) => "ClientError".into(),
180            Self::DatabaseNotReady => "DatabaseNotReady".into(),
181            Self::EmptyRatiosError => "EmptyRatiosError".into(),
182            Self::EvaluationError(..) => "EvaluationError".into(),
183            Self::IOError(e) => format!("IOError({:?})", e.kind()).into(),
184            Self::InternalError(..) => "InternalError".into(),
185            Self::InvalidExperimentFormat => "InvalidExperimentFormat".into(),
186            Self::InvalidExpression => "InvalidExpression".into(),
187            Self::InvalidFraction => "InvalidFraction".into(),
188            Self::InvalidPath(..) => "InvalidPath".into(),
189            Self::InvalidPersistedData => "InvalidPersistedData".into(),
190            Self::JSONError(..) => "JSONError".into(),
191            Self::NoSuchBranch(..) => "NoSuchBranch".into(),
192            Self::NoSuchExperiment(..) => "NoSuchExperiment".into(),
193            Self::OutOfBoundsError => "OutOfBoundsError".into(),
194            Self::ParseIntError(..) => "ParseIntError".into(),
195            Self::RegexError(..) => "RegexError".into(),
196            Self::RkvError(e) => format!("RkvError({})", e.error_code()).into(),
197            Self::TransformParameterError(..) => "TransformParameterError".into(),
198            Self::TryFromIntError(..) => "TryFromIntError".into(),
199            Self::TryFromSliceError(..) => "TryFromSliceError".into(),
200            Self::UniFFICallbackError(..) => "UniFFICallbackError".into(),
201            Self::UrlParsingError(..) => "UrlParsingError".into(),
202            Self::UuidError(..) => "UuidError".into(),
203            Self::VersionParsingError(..) => "VersionParsingError".into(),
204        }
205    }
206}
207
208#[cfg(feature = "stateful")]
209impl ErrorCode for rkv::StoreError {
210    fn error_code(&self) -> Cow<'static, str> {
211        match self {
212            Self::ManagerPoisonError => "ManagerPoisonError".into(),
213            Self::DatabaseCorrupted => "DatabaseCorrupted".into(),
214            Self::KeyValuePairNotFound => "KeyValuePairNotFound".into(),
215            Self::KeyValuePairBadSize => "KeyValuePairBadSize".into(),
216            Self::FileInvalid => "FileInvalid".into(),
217            Self::MapFull => "MapFull".into(),
218            Self::DbsFull => "DbsFull".into(),
219            Self::ReadersFull => "ReadersFull".into(),
220            Self::IoError(e) => format!("IoError({:?})", e.kind()).into(),
221            Self::UnsuitableEnvironmentPath(..) => "UnsuitableEnvironmentPath".into(),
222            Self::DataError(..) => "DataError".into(),
223            Self::SafeModeError(..) => "SafeModeError".into(),
224            Self::ReadTransactionAlreadyExists(..) => "ReadTransactionAlreadyExists".into(),
225            Self::OpenAttemptedDuringTransaction(..) => "OpenAttemptedDuringTransaction".into(),
226        }
227    }
228}
229
230#[cfg(feature = "stateful")]
231impl ErrorCode for BehaviorError {
232    fn error_code(&self) -> Cow<'static, str> {
233        match self {
234            Self::EventQueryParseError(..) => "EventQueryParseError",
235            Self::EventQueryTypeParseError(..) => "EventQueryTypeParseError",
236            Self::IntervalParseError(..) => "IntervalParseError",
237            Self::InvalidDuration(..) => "InvalidDuration",
238            Self::InvalidState(..) => "InvalidState",
239            Self::MissingEventStore => "MissingEventStore",
240            Self::MissingRecordedContext => "MissingRecordedContext",
241            Self::TypeError(..) => "TypeError",
242        }
243        .into()
244    }
245}