1use std::{convert::Infallible, error::Error as ErrorTrait, fmt};
8
9use crate::{
10 auth::AuthenticationClientFeedback,
11 ffi_sdk::{self, ffi_utils::repr_c},
12};
13
14pub type Result<Ok, Err = DittoError> = ::core::result::Result<Ok, Err>;
16
17#[doc(inline)]
20pub use ffi_sdk::FfiErrorCode as CoreApiErrorKind;
21
22pub 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)]
81pub(crate) enum LicenseTokenError {
83 VerificationFailed { message: String },
84 Expired { message: String },
85 UnsupportedFutureVersion { message: String },
86}
87
88impl LicenseTokenError {
89 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]
101pub enum ErrorKind {
103 Authentication,
105
106 Config, FfiLegacy, Internal,
114
115 InvalidInput, IO, License, NotActivated, NonExtant, ReleasedDittoInstance,
133
134 CoreApi(CoreApiErrorKind),
136
137 Deprecation,
139}
140
141mod legacy {
144 #[derive(Debug)]
145 pub(crate) struct FfiError {
149 pub(crate) code: i32,
150 pub(crate) msg: String,
151 }
152}
153
154pub(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 let msg = format!("{:?} - {}", &self.kind, self.kind);
164 fmt.debug_struct("RustError")
165 .field("kind", &msg)
166 .field("error", &self.error) .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 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 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 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 pub(crate) fn license(err: LicenseTokenError) -> Self {
271 DittoError {
272 repr: Repr::License(err),
273 }
274 }
275
276 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 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, Repr::License(_) => ErrorKind::License,
295 Repr::Authentication(_) => ErrorKind::Authentication,
296 Repr::Ffi(ffi) => ErrorKind::CoreApi(ffi.code),
297 }
298 }
299
300 pub fn encryption_error_kind(&self) -> Option<CoreApiErrorKind> {
317 match self.kind() {
318 ErrorKind::CoreApi(
319 code @ (CoreApiErrorKind::EncryptionKeyRequired
320 | CoreApiErrorKind::EncryptionKeyUnexpected
321 | CoreApiErrorKind::EncryptionKeyInvalid
322 | CoreApiErrorKind::EncryptionUnsupportedScheme
323 | CoreApiErrorKind::EncryptionMetadataCorrupt),
324 ) => Some(code),
325 _ => None,
326 }
327 }
328
329 pub fn is_encryption_error(&self) -> bool {
335 self.encryption_error_kind().is_some()
336 }
337
338 pub fn get_authentication_client_feedback(&self) -> Option<AuthenticationClientFeedback> {
343 if let Repr::Authentication(ref feedback) = self.repr {
344 Some(feedback.clone())
345 } else {
346 None
347 }
348 }
349
350 pub(crate) fn from_authentication_feedback(feedback: AuthenticationClientFeedback) -> Self {
352 DittoError {
353 repr: Repr::Authentication(feedback),
354 }
355 }
356}
357
358impl fmt::Display for DittoError {
359 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
360 match self.repr {
361 Repr::Simple(kind) => write!(fmt, "{}", kind),
362 Repr::Rust(ref e) => e.error.fmt(fmt), Repr::FfiLegacy(ref c) => write!(fmt, "{} (code {})", c.msg, c.code),
364 Repr::License(ref e) => write!(fmt, "{}", e.message()),
365 Repr::Authentication(ref feedback) => match feedback.feedback {
366 Some(ref feedback) => {
367 write!(fmt, "Authentication Error with feedback: {}", feedback)
368 }
369 None => {
370 write!(fmt, "Authentication Error")
371 }
372 },
373 Repr::Ffi(ref ffi_error) => ffi_error.fmt(fmt),
374 }
375 }
376}
377
378error_from_i32! {
379 i32, ::core::num::NonZeroI32
380}
381#[rustfmt::skip]
382macro_rules! error_from_i32 {(
383 $( $i32:ty ),* $(,)?
384) => (
385 $(
386 impl From<$i32> for legacy::FfiError {
387 fn from(code: $i32) -> legacy::FfiError {
388 let code: i32 = code.into();
389 debug_assert_ne!(code, 0);
390 match ffi_sdk::ditto_error_message() {
391 Some(c_msg) => {
392 let msg = c_msg.into_string();
393 legacy::FfiError { code, msg }
394 }
395 None => legacy::FfiError {
396 msg: "No Message".to_owned(),
397 code,
398 },
399 }
400 }
401 }
402
403 impl From<$i32> for DittoError {
404 fn from(code: $i32) -> Self {
405 DittoError { repr: Repr::FfiLegacy(code.into()) }
406 }
407 }
408 )*
409)}
410use error_from_i32;
411
412impl From<::serde_cbor::Error> for DittoError {
413 fn from(err: ::serde_cbor::Error) -> Self {
414 DittoError::new(ErrorKind::InvalidInput, err)
415 }
416}
417
418impl From<::serde_json::Error> for DittoError {
419 fn from(err: ::serde_json::Error) -> Self {
420 DittoError::new(ErrorKind::InvalidInput, err)
421 }
422}
423
424impl From<::std::io::Error> for DittoError {
425 fn from(err: ::std::io::Error) -> Self {
426 DittoError::new(ErrorKind::IO, err)
427 }
428}
429
430impl From<Infallible> for DittoError {
431 fn from(err: Infallible) -> Self {
432 DittoError::new(ErrorKind::Internal, err)
433 }
434}
435
436impl From<repr_c::Box_<ffi_sdk::FfiError>> for DittoError {
437 fn from(raw: repr_c::Box<ffi_sdk::FfiError>) -> Self {
438 DittoError {
439 repr: Repr::Ffi(FfiError {
440 code: ::ffi_sdk::dittoffi_error_code(&*raw),
441 raw,
442 }),
443 }
444 }
445}
446
447impl From<::tokio::task::JoinError> for DittoError {
448 fn from(err: ::tokio::task::JoinError) -> Self {
449 DittoError::new(ErrorKind::Internal, err)
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456
457 const ENCRYPTION_KINDS: &[CoreApiErrorKind] = &[
460 CoreApiErrorKind::EncryptionKeyRequired,
461 CoreApiErrorKind::EncryptionKeyUnexpected,
462 CoreApiErrorKind::EncryptionKeyInvalid,
463 CoreApiErrorKind::EncryptionUnsupportedScheme,
464 CoreApiErrorKind::EncryptionMetadataCorrupt,
465 ];
466
467 #[test]
468 fn encryption_helpers_match_every_encryption_kind() {
469 for &code in ENCRYPTION_KINDS {
470 let err = DittoError::from(ErrorKind::CoreApi(code));
471 assert!(
472 err.is_encryption_error(),
473 "is_encryption_error() should be true for {code:?}"
474 );
475 assert_eq!(
476 err.encryption_error_kind(),
477 Some(code),
478 "encryption_error_kind() should return the same kind for {code:?}"
479 );
480 }
481 }
482
483 #[test]
484 fn encryption_helpers_reject_non_encryption_kinds() {
485 let non_encryption: &[ErrorKind] = &[
488 ErrorKind::IO,
489 ErrorKind::CoreApi(CoreApiErrorKind::StoreQuery),
490 ];
491 for &kind in non_encryption {
492 let err = DittoError::from(kind);
493 assert!(
494 !err.is_encryption_error(),
495 "is_encryption_error() should be false for {kind:?}"
496 );
497 assert_eq!(
498 err.encryption_error_kind(),
499 None,
500 "encryption_error_kind() should be None for {kind:?}"
501 );
502 }
503 }
504}