dittolive_ditto/
error.rs

1//! Ditto Error Types
2//!
3//! Modeled after [`::std::io::Error`].
4//!
5//! [`DittoError`] is the main error type used in the SDK.
6
7use std::{convert::Infallible, error::Error as ErrorTrait, fmt};
8
9use crate::{
10    ffi_sdk::{self, ffi_utils::repr_c},
11    identity::AuthenticationClientFeedback,
12};
13
14/// Custom result type for Ditto
15pub type Result<Ok, Err = DittoError> = ::core::result::Result<Ok, Err>;
16
17// FIXME(Daniel & Ham): properly clean up the errors of the Rust SDK in the next
18// breaking version, _e.g._, V5.
19#[doc(inline)]
20pub use ffi_sdk::FfiErrorCode as CoreApiErrorKind;
21
22/// Custom error for Ditto.
23///
24/// ### Recoverable errors
25///
26/// Use its [`.kind()`][Self::kind()] method to be able to `match` on a bunch of
27/// specific known [`ErrorKind`]s.
28///
29///   - ⚠️ be aware that the list of possible kinds is **not exhaustive**.
30///
31/// ### Unexpected/unrecoverable errors
32///
33/// Can be `{}`-[`Display`][::core::fmt::Display]ed for a human-friendly message
34/// and representation.
35pub struct DittoError {
36    repr: Repr,
37}
38
39pub(crate) struct FfiError {
40    pub(crate) code: ::ffi_sdk::FfiErrorCode,
41    raw: repr_c::Box<::ffi_sdk::FfiError>,
42}
43
44impl ::core::fmt::Debug for FfiError {
45    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
46        f.debug_struct("FfiError")
47            .field("code", &self.code)
48            .field(
49                "description",
50                &::ffi_sdk::dittoffi_error_description(&self.raw),
51            )
52            .finish_non_exhaustive()
53    }
54}
55
56impl ::core::fmt::Display for FfiError {
57    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
58        let Self { code, raw } = self;
59        write!(
60            f,
61            "{code:?}: {}",
62            ::ffi_sdk::dittoffi_error_description(raw)
63        )?;
64        Ok(())
65    }
66}
67
68impl ::std::error::Error for FfiError {}
69
70#[derive(Debug)]
71enum Repr {
72    Simple(ErrorKind),
73    Authentication(AuthenticationClientFeedback),
74    Ffi(FfiError),
75    FfiLegacy(legacy::FfiError),
76    Rust(RustError),
77    License(LicenseTokenError),
78}
79
80#[derive(Debug)]
81/// Error related to License
82pub(crate) enum LicenseTokenError {
83    VerificationFailed { message: String },
84    Expired { message: String },
85    UnsupportedFutureVersion { message: String },
86}
87
88impl LicenseTokenError {
89    /// Returns the internal error message for more details
90    pub fn message(&self) -> &String {
91        match self {
92            LicenseTokenError::VerificationFailed { message } => message,
93            LicenseTokenError::Expired { message } => message,
94            LicenseTokenError::UnsupportedFutureVersion { message } => message,
95        }
96    }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq)]
100#[non_exhaustive]
101/// General kind of an error
102pub enum ErrorKind {
103    /// Configured Authentication provider was not found or failed to authenticate
104    Authentication,
105
106    /// Required configuration was missing or invalid
107    Config, // use to distinguish misconfigured from invalid query/document/etc.
108
109    /// Error originated in the FFI layer
110    FfiLegacy, // use this when you can't re-map into a Rust Error
111
112    /// An error occurred in Ditto's internals
113    Internal,
114
115    /// Provided parameters or data are not valid
116    InvalidInput, // use when user input fails validation
117
118    /// Error with the local file system or networking
119    IO, // re-maps std::io::error types (Filesystem and Network), including missing local files
120
121    /// The Ditto license is missing, expired, or otherwise invalid
122    License, // specifically when the License is not valid
123
124    /// Ditto sync was started without a valid license
125    NotActivated, // When Sync started before license validation
126
127    /// The requested resource *no longer* exists (i.e. lost or destroyed)
128    /// Used typically for empty query results
129    NonExtant, // when a requested resource doesn't exist
130
131    /// The requested Ditto instance has already been dropped
132    ReleasedDittoInstance,
133
134    /// An error occurred with one of the core APIs of Ditto.
135    CoreApi(CoreApiErrorKind),
136
137    /// Deprecation error
138    Deprecation,
139}
140
141/// A datastructure representing an error from the FFI using the legacy mechanisms
142/// (thread-local storage and integer error code).
143mod legacy {
144    #[derive(Debug)]
145    /// Errors originating in FFI code.
146    ///
147    /// Shadows the FFI's CError type which doesn't have a C Repr.
148    pub(crate) struct FfiError {
149        pub(crate) code: i32,
150        pub(crate) msg: String,
151    }
152}
153
154/// Errors originating in Rust Code
155pub(crate) struct RustError {
156    pub(crate) kind: ErrorKind,
157    pub(crate) error: Box<dyn ErrorTrait + Send + Sync>,
158}
159
160impl fmt::Debug for RustError {
161    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
162        // Display both the ErrorKind Varriend but also the message text
163        let msg = format!("{:?} - {}", &self.kind, self.kind);
164        fmt.debug_struct("RustError")
165            .field("kind", &msg)
166            .field("error", &self.error) // Then also display underlying cause info
167            .finish()
168    }
169}
170
171impl ErrorKind {
172    fn as_str(&self) -> &'static str {
173        match *self {
174            ErrorKind::Authentication => "Unable to authenticate Ditto",
175            ErrorKind::Config => "Required configuration values are missing or invalid",
176            ErrorKind::FfiLegacy => "Unmapped Ditto Error",
177            ErrorKind::IO => "There is a problem with the underlying file, directory, or network socket",
178            ErrorKind::Internal => "Ditto encountered an internal error",
179            ErrorKind::InvalidInput => "Invalid client input provided",
180            ErrorKind::License => "License token error",
181            ErrorKind::NotActivated => "Sync could not be started because Ditto has not yet been activated. This can be achieved with a successful call to `set_license_token`. If you need to obtain a license token then please visit https://portal.ditto.live.",
182            ErrorKind::NonExtant => "The target entity can no longer be found",
183            ErrorKind::ReleasedDittoInstance => "The related Ditto instance has been closed",
184            ErrorKind::CoreApi(_) => "\
185                an error occurred from core Ditto functionality. \
186                Please use the `Display` implementation for more info.\
187            ",
188            ErrorKind::Deprecation => "Deprecated method called",
189        }
190    }
191}
192
193impl ::core::fmt::Display for ErrorKind {
194    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
195        match self {
196            ErrorKind::CoreApi(ffi_error_code) => write!(f, "{ffi_error_code:?}"),
197            _ => self.as_str().fmt(f),
198        }
199    }
200}
201
202impl From<ErrorKind> for DittoError {
203    fn from(kind: ErrorKind) -> DittoError {
204        DittoError {
205            repr: Repr::Simple(kind),
206        }
207    }
208}
209
210impl fmt::Debug for DittoError {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        fmt::Debug::fmt(&self.repr, f)
213    }
214}
215
216impl ErrorTrait for DittoError {}
217
218impl DittoError {
219    /// Construct an error from an existing Error creating an error chain
220    pub(crate) fn new<E>(kind: ErrorKind, rust_err: E) -> Self
221    where
222        E: Into<Box<dyn ErrorTrait + Send + Sync>>,
223    {
224        DittoError {
225            repr: Repr::Rust(RustError {
226                kind,
227                error: rust_err.into(),
228            }),
229        }
230    }
231}
232
233impl DittoError {
234    /// Construct an error with a specific message from a string
235    pub(crate) fn from_str(kind: ErrorKind, msg: impl Into<String>) -> DittoError {
236        let msg: String = msg.into();
237        DittoError {
238            repr: Repr::Rust(RustError {
239                kind,
240                error: msg.into(),
241            }),
242        }
243    }
244
245    /// Construct an error from a small peer info error code
246    pub(crate) fn from_small_peer_info_error_code(error_code: i32) -> DittoError {
247        match error_code {
248            -1 => Self::from_str(
249                ErrorKind::InvalidInput,
250                "The observability subsystem is unavailable",
251            ),
252            1 => Self::from_str(
253                ErrorKind::InvalidInput,
254                "The amount of data is too large according to our self-imposed limits.",
255            ),
256            2 => Self::from_str(
257                ErrorKind::InvalidInput,
258                "The amount of JSON data is too nested acccording to our self-imposed limits, or \
259                 if the data cannot be parsed to determine the depth.",
260            ),
261            3 => Self::from_str(
262                ErrorKind::InvalidInput,
263                "The data cannot be parsed as a Map<String, Value>.",
264            ),
265            _ => Self::from_str(ErrorKind::FfiLegacy, "Unmapped error"),
266        }
267    }
268
269    /// Construct an error from a LicenseTokenError
270    pub(crate) fn license(err: LicenseTokenError) -> Self {
271        DittoError {
272            repr: Repr::License(err),
273        }
274    }
275
276    /// Manually specify the Error Kind, but fetch the message from the FFI.
277    ///
278    /// The result is returned as a Rust style error with the cause of `<String as ErrorTrait>`
279    pub(crate) fn from_ffi(kind: ErrorKind) -> Self {
280        let msg = match ffi_sdk::ditto_error_message() {
281            Some(c_msg) => c_msg.into_string(),
282            None => "no message".into(),
283        };
284        DittoError::new(kind, msg)
285    }
286
287    /// Return the kind of the Error
288    pub fn kind(&self) -> ErrorKind {
289        match &self.repr {
290            Repr::Simple(kind) => *kind,
291            Repr::Rust(e) => e.kind,
292            Repr::FfiLegacy(_c) => ErrorKind::FfiLegacy, // could implement uniform code to kind
293            // mapping in future
294            Repr::License(_) => ErrorKind::License,
295            Repr::Authentication(_) => ErrorKind::Authentication,
296            Repr::Ffi(ffi) => ErrorKind::CoreApi(ffi.code),
297        }
298    }
299
300    /// If this is an Authentication error, get feedback from the auth provider.
301    ///
302    /// The returned value may contain arbitrary JSON from the auth service
303    /// with details describing the success or rejection of the authentication request.
304    pub fn get_authentication_client_feedback(&self) -> Option<AuthenticationClientFeedback> {
305        if let Repr::Authentication(ref feedback) = self.repr {
306            Some(feedback.clone())
307        } else {
308            None
309        }
310    }
311
312    // We may want to evolve how this is constructed, so let's not expose it to customers yet
313    pub(crate) fn from_authentication_feedback(feedback: AuthenticationClientFeedback) -> Self {
314        DittoError {
315            repr: Repr::Authentication(feedback),
316        }
317    }
318}
319
320impl fmt::Display for DittoError {
321    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
322        match self.repr {
323            Repr::Simple(kind) => write!(fmt, "{}", kind),
324            Repr::Rust(ref e) => e.error.fmt(fmt), // forward to cause error
325            Repr::FfiLegacy(ref c) => write!(fmt, "{} (code {})", c.msg, c.code),
326            Repr::License(ref e) => write!(fmt, "{}", e.message()),
327            Repr::Authentication(ref feedback) => match feedback.feedback {
328                Some(ref feedback) => {
329                    write!(fmt, "Authentication Error with feedback: {}", feedback)
330                }
331                None => {
332                    write!(fmt, "Authentication Error")
333                }
334            },
335            Repr::Ffi(ref ffi_error) => ffi_error.fmt(fmt),
336        }
337    }
338}
339
340error_from_i32! {
341    i32, ::core::num::NonZeroI32
342}
343#[rustfmt::skip]
344macro_rules! error_from_i32 {(
345    $( $i32:ty ),* $(,)?
346) => (
347    $(
348        impl From<$i32> for legacy::FfiError {
349            fn from(code: $i32) -> legacy::FfiError {
350                let code: i32 = code.into();
351                debug_assert_ne!(code, 0);
352                match ffi_sdk::ditto_error_message() {
353                    Some(c_msg) => {
354                        let msg = c_msg.into_string();
355                        legacy::FfiError { code, msg }
356                    }
357                    None => legacy::FfiError {
358                        msg: "No Message".to_owned(),
359                        code,
360                    },
361                }
362            }
363        }
364
365        impl From<$i32> for DittoError {
366            fn from(code: $i32) -> Self {
367                DittoError { repr: Repr::FfiLegacy(code.into()) }
368            }
369        }
370    )*
371)}
372use error_from_i32;
373
374impl From<::serde_cbor::Error> for DittoError {
375    fn from(err: ::serde_cbor::Error) -> Self {
376        DittoError::new(ErrorKind::InvalidInput, err)
377    }
378}
379
380impl From<::serde_json::Error> for DittoError {
381    fn from(err: ::serde_json::Error) -> Self {
382        DittoError::new(ErrorKind::InvalidInput, err)
383    }
384}
385
386impl From<::std::io::Error> for DittoError {
387    fn from(err: ::std::io::Error) -> Self {
388        DittoError::new(ErrorKind::IO, err)
389    }
390}
391
392impl From<Infallible> for DittoError {
393    fn from(err: Infallible) -> Self {
394        DittoError::new(ErrorKind::Internal, err)
395    }
396}
397
398impl From<repr_c::Box_<ffi_sdk::FfiError>> for DittoError {
399    fn from(raw: repr_c::Box<ffi_sdk::FfiError>) -> Self {
400        DittoError {
401            repr: Repr::Ffi(FfiError {
402                code: ::ffi_sdk::dittoffi_error_code(&*raw),
403                raw,
404            }),
405        }
406    }
407}
408
409impl From<::tokio::task::JoinError> for DittoError {
410    fn from(err: ::tokio::task::JoinError) -> Self {
411        DittoError::new(ErrorKind::Internal, err)
412    }
413}