1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! Provides [`FdbError`] type, [`FdbResult`] type alias and error
//! constants.

use std::convert::TryInto;
use std::error::Error;
use std::fmt::{self, Display};

use crate::option::ErrorPredicate;

/// Error type for this crate.
///
/// Internally it wraps FDB [Error Codes]. Error codes from 100 thru'
/// 999 is generated by the binding layer and not the C API.
///
/// [Error Codes]: https://apple.github.io/foundationdb/api-error-codes.html
//
// NOTE: 0 cannot be used for `error_code`.
//
// 100 - `database` module
// 110 - `tuple` module
// 120 - `subspace` module
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct FdbError {
    /// FoundationDB error code `fdb_error_t`
    error_code: i32,
}

/// Error occurred while opening database.
pub const DATABASE_OPEN: i32 = 100;

/// Error occurred while getting a value from the tuple.
pub const TUPLE_GET: i32 = 110;

/// Error occurred extracting a [`Tuple`] value from [`Bytes`].
///
/// [`Tuple`]: crate::tuple::Tuple
/// [`Bytes`]: bytes::Bytes
pub const TUPLE_TRY_FROM_BYTES: i32 = 111;

/// Error occurred extracting a [`Tuple`] value from [`Key`].
///
/// [`Tuple`]: crate::tuple::Tuple
/// [`Key`]: crate::Key
pub const TUPLE_TRY_FROM_KEY: i32 = 112;

/// Error occurred extracting a [`Tuple`] value from [`Value`].
///
/// [`Tuple`]: crate::tuple::Tuple
/// [`Value`]: crate::Value
pub const TUPLE_TRY_FROM_VALUE: i32 = 113;

/// Error occured when trying to pack [`Tuple`] containing an
/// incomplete [`Versionstamp`]. No incomplete [`Versionstamp`] found.
///
/// [`Tuple`]:  crate::tuple::Tuple
/// [`Versionstamp`]: crate::tuple::Versionstamp
pub const TUPLE_PACK_WITH_VERSIONSTAMP_NOT_FOUND: i32 = 114;

/// Error occured when trying to pack [`Tuple`] containing an
/// incomplete [`Versionstamp`]. Multiple incomplete [`Versionstamp`]
/// found.
///
/// [`Tuple`]:  crate::tuple::Tuple
/// [`Versionstamp`]: crate::tuple::Versionstamp
pub const TUPLE_PACK_WITH_VERSIONSTAMP_MULTIPLE_FOUND: i32 = 115;

/// Error occurred when calling [`strinc`], as the `prefix` supplied
/// is either empty or contains only `0xFF`.
///
/// [`strinc`]: crate::tuple::key_util::strinc
pub const TUPLE_KEY_UTIL_STRINC_ERROR: i32 = 116;

/// TODO
pub const TUPLE_VERSIONSTAMP_TRY_FROM: i32 = 117;

/// Error occured when trying to pack [`Subspace`] containing an
/// incomplete [`Versionstamp`]. Prefix contains an incomplete
/// [`Versionstamp`], which is not allowed.
///
/// [`Subspace`]:  crate::subspace::Subspace
/// [`Versionstamp`]: crate::tuple::Versionstamp
pub const SUBSPACE_PACK_WITH_VERSIONSTAMP_PREFIX_INCOMPLETE: i32 = 120;

/// Error occured when trying to unpack a key. The provided key is not
/// contained in the [`Subspace`].
///
/// [`Subspace`]:  crate::subspace::Subspace
pub const SUBSPACE_UNPACK_KEY_MISMATCH: i32 = 121;

/// Alias for [`Result`]`<T,`[`FdbError`]`>`
///
/// [`Result`]: std::result::Result
/// [`FdbError`]: crate::error::FdbError
pub type FdbResult<T> = Result<T, FdbError>;

impl FdbError {
    /// Create new [`FdbError`]
    pub fn new(err: i32) -> FdbError {
        FdbError { error_code: err }
    }

    /// Returns raw FDB error code
    pub fn code(self) -> i32 {
        self.error_code
    }

    /// Returns `true` if the error indicates the operations in the
    /// transactions should be retried because of transient error.
    pub fn is_retryable(&self) -> bool {
        unsafe {
            // `FDB_ERROR_PREDICATE_RETRYABLE` has a value `50000`
            // which can safely be converted into an `i32`.
            //
            // non-zero is `true`.
            fdb_sys::fdb_error_predicate(
                ErrorPredicate::Retryable.code().try_into().unwrap(),
                self.error_code,
            ) != 0
        }
    }

    /// Returns true if the error indicates the transaction may have
    /// succeeded, though not in a way the system can verify.
    pub fn is_maybe_committed(&self) -> bool {
        unsafe {
            // `FDB_ERROR_PREDICATE_MAYBE_COMMITTED` has a value
            // `50001` which can safely be converted into an `i32`.
            //
            // non-zero is `true`.
            fdb_sys::fdb_error_predicate(
                ErrorPredicate::MaybeCommitted.code().try_into().unwrap(),
                self.error_code,
            ) != 0
        }
    }

    /// Returns `true` if the error indicates the transaction has not
    /// committed, though in a way that can be retried.
    pub fn is_retryable_not_committed(&self) -> bool {
        unsafe {
            // `FDB_ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED` has a
            // value `50002` which can safely be converted into an
            // `i32`.
            //
            // non-zero is `true`.
            fdb_sys::fdb_error_predicate(
                ErrorPredicate::RetryableNotCommitted
                    .code()
                    .try_into()
                    .unwrap(),
                self.error_code,
            ) != 0
        }
    }

    /// Returns `true` if the error is from a layer. Returns `false`
    /// if the error is a [C binding] error
    ///
    /// [C binding]: https://apple.github.io/foundationdb/api-error-codes.html
    pub(crate) fn layer_error(e: i32) -> bool {
        (100..=999).contains(&e)
    }
}

impl Error for FdbError {}

impl Display for FdbError {
    fn fmt<'a>(&self, f: &mut fmt::Formatter<'a>) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

/// Converts `fdb_error_t` to `FdbResult`
pub(crate) fn check(err: fdb_sys::fdb_error_t) -> FdbResult<()> {
    if err == 0 {
        Ok(())
    } else {
        Err(FdbError::new(err))
    }
}