Skip to main content

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    #[deprecated(note = "use the `Display` implementation instead")]
173    #[doc(hidden)]
174    pub fn as_str(&self) -> &'static str {
175        match *self {
176            ErrorKind::Authentication => "Unable to authenticate Ditto",
177            ErrorKind::Config => "Required configuration values are missing or invalid",
178            ErrorKind::FfiLegacy => "Unmapped Ditto Error",
179            ErrorKind::IO => "There is a problem with the underlying file, directory, or network socket",
180            ErrorKind::Internal => "Ditto encountered an internal error",
181            ErrorKind::InvalidInput => "Invalid client input provided",
182            ErrorKind::License => "License token error",
183            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.",
184            ErrorKind::NonExtant => "The target entity can no longer be found",
185            ErrorKind::ReleasedDittoInstance => "The related Ditto instance has been closed",
186            ErrorKind::CoreApi(_) => "\
187                an error occurred from core Ditto functionality. \
188                Please use the `Display` implementation for more info.\
189            ",
190            ErrorKind::Deprecation => "Deprecated method called",
191        }
192    }
193}
194
195impl ::core::fmt::Display for ErrorKind {
196    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
197        match self {
198            ErrorKind::CoreApi(ffi_error_code) => write!(f, "{ffi_error_code:?}"),
199            _ => {
200                #[allow(deprecated)]
201                {
202                    self.as_str()
203                }
204            }
205            .fmt(f),
206        }
207    }
208}
209
210impl From<ErrorKind> for DittoError {
211    fn from(kind: ErrorKind) -> DittoError {
212        DittoError {
213            repr: Repr::Simple(kind),
214        }
215    }
216}
217
218impl fmt::Debug for DittoError {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        fmt::Debug::fmt(&self.repr, f)
221    }
222}
223
224impl ErrorTrait for DittoError {}
225
226impl DittoError {
227    /// Construct an error from an existing Error creating an error chain
228    pub(crate) fn new<E>(kind: ErrorKind, rust_err: E) -> Self
229    where
230        E: Into<Box<dyn ErrorTrait + Send + Sync>>,
231    {
232        DittoError {
233            repr: Repr::Rust(RustError {
234                kind,
235                error: rust_err.into(),
236            }),
237        }
238    }
239}
240
241impl DittoError {
242    /// Construct an error with a specific message from a string
243    pub(crate) fn from_str(kind: ErrorKind, msg: impl Into<String>) -> DittoError {
244        let msg: String = msg.into();
245        DittoError {
246            repr: Repr::Rust(RustError {
247                kind,
248                error: msg.into(),
249            }),
250        }
251    }
252
253    /// Construct an error from a small peer info error code
254    pub(crate) fn from_small_peer_info_error_code(error_code: i32) -> DittoError {
255        match error_code {
256            -1 => Self::from_str(
257                ErrorKind::InvalidInput,
258                "The observability subsystem is unavailable",
259            ),
260            1 => Self::from_str(
261                ErrorKind::InvalidInput,
262                "The amount of data is too large according to our self-imposed limits.",
263            ),
264            2 => Self::from_str(
265                ErrorKind::InvalidInput,
266                "The amount of JSON data is too nested acccording to our self-imposed limits, or \
267                 if the data cannot be parsed to determine the depth.",
268            ),
269            3 => Self::from_str(
270                ErrorKind::InvalidInput,
271                "The data cannot be parsed as a Map<String, Value>.",
272            ),
273            _ => Self::from_str(ErrorKind::FfiLegacy, "Unmapped error"),
274        }
275    }
276
277    /// Construct an error from a LicenseTokenError
278    pub(crate) fn license(err: LicenseTokenError) -> Self {
279        DittoError {
280            repr: Repr::License(err),
281        }
282    }
283
284    /// Manually specify the Error Kind, but fetch the message from the FFI.
285    ///
286    /// The result is returned as a Rust style error with the cause of `<String as ErrorTrait>`
287    pub(crate) fn from_ffi(kind: ErrorKind) -> Self {
288        let msg = match ffi_sdk::ditto_error_message() {
289            Some(c_msg) => c_msg.into_string(),
290            None => "no message".into(),
291        };
292        DittoError::new(kind, msg)
293    }
294
295    /// Return the kind of the Error
296    pub fn kind(&self) -> ErrorKind {
297        match &self.repr {
298            Repr::Simple(kind) => *kind,
299            Repr::Rust(e) => e.kind,
300            Repr::FfiLegacy(_c) => ErrorKind::FfiLegacy, // could implement uniform code to kind
301            // mapping in future
302            Repr::License(_) => ErrorKind::License,
303            Repr::Authentication(_) => ErrorKind::Authentication,
304            Repr::Ffi(ffi) => ErrorKind::CoreApi(ffi.code),
305        }
306    }
307
308    /// If this is an Authentication error, get feedback from the auth provider.
309    ///
310    /// The returned value may contain arbitrary JSON from the auth service
311    /// with details describing the success or rejection of the authentication request.
312    pub fn get_authentication_client_feedback(&self) -> Option<AuthenticationClientFeedback> {
313        if let Repr::Authentication(ref feedback) = self.repr {
314            Some(feedback.clone())
315        } else {
316            None
317        }
318    }
319
320    // We may want to evolve how this is constructed, so let's not expose it to customers yet
321    pub(crate) fn from_authentication_feedback(feedback: AuthenticationClientFeedback) -> Self {
322        DittoError {
323            repr: Repr::Authentication(feedback),
324        }
325    }
326}
327
328impl fmt::Display for DittoError {
329    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
330        match self.repr {
331            Repr::Simple(kind) => write!(fmt, "{}", kind),
332            Repr::Rust(ref e) => e.error.fmt(fmt), // forward to cause error
333            Repr::FfiLegacy(ref c) => write!(fmt, "{} (code {})", c.msg, c.code),
334            Repr::License(ref e) => write!(fmt, "{}", e.message()),
335            Repr::Authentication(ref feedback) => match feedback.feedback {
336                Some(ref feedback) => {
337                    write!(fmt, "Authentication Error with feedback: {}", feedback)
338                }
339                None => {
340                    write!(fmt, "Authentication Error")
341                }
342            },
343            Repr::Ffi(ref ffi_error) => ffi_error.fmt(fmt),
344        }
345    }
346}
347
348error_from_i32! {
349    i32, ::core::num::NonZeroI32
350}
351#[rustfmt::skip]
352macro_rules! error_from_i32 {(
353    $( $i32:ty ),* $(,)?
354) => (
355    $(
356        impl From<$i32> for legacy::FfiError {
357            fn from(code: $i32) -> legacy::FfiError {
358                let code: i32 = code.into();
359                debug_assert_ne!(code, 0);
360                match ffi_sdk::ditto_error_message() {
361                    Some(c_msg) => {
362                        let msg = c_msg.into_string();
363                        legacy::FfiError { code, msg }
364                    }
365                    None => legacy::FfiError {
366                        msg: "No Message".to_owned(),
367                        code,
368                    },
369                }
370            }
371        }
372
373        impl From<$i32> for DittoError {
374            fn from(code: $i32) -> Self {
375                DittoError { repr: Repr::FfiLegacy(code.into()) }
376            }
377        }
378    )*
379)}
380use error_from_i32;
381
382impl From<::serde_cbor::Error> for DittoError {
383    fn from(err: ::serde_cbor::Error) -> Self {
384        DittoError::new(ErrorKind::InvalidInput, err)
385    }
386}
387
388impl From<::serde_json::Error> for DittoError {
389    fn from(err: ::serde_json::Error) -> Self {
390        DittoError::new(ErrorKind::InvalidInput, err)
391    }
392}
393
394impl From<::std::io::Error> for DittoError {
395    fn from(err: ::std::io::Error) -> Self {
396        DittoError::new(ErrorKind::IO, err)
397    }
398}
399
400impl From<Infallible> for DittoError {
401    fn from(err: Infallible) -> Self {
402        DittoError::new(ErrorKind::Internal, err)
403    }
404}
405
406impl From<repr_c::Box_<ffi_sdk::FfiError>> for DittoError {
407    fn from(raw: repr_c::Box<ffi_sdk::FfiError>) -> Self {
408        DittoError {
409            repr: Repr::Ffi(FfiError {
410                code: ::ffi_sdk::dittoffi_error_code(&*raw),
411                raw,
412            }),
413        }
414    }
415}
416
417impl From<::tokio::task::JoinError> for DittoError {
418    fn from(err: ::tokio::task::JoinError) -> Self {
419        DittoError::new(ErrorKind::Internal, err)
420    }
421}