discord_cassandra_cpp/cassandra/
error.rs

1use crate::cassandra::consistency::Consistency;
2use crate::cassandra::util::{Protected, ProtectedInner};
3use crate::cassandra::value::ValueType;
4use crate::cassandra::write_type::WriteType;
5
6use crate::cassandra_sys::cass_error_desc;
7use crate::cassandra_sys::cass_error_result_code;
8use crate::cassandra_sys::cass_error_result_free;
9use crate::cassandra_sys::cass_future_error_code;
10use crate::cassandra_sys::cass_future_error_message;
11use crate::cassandra_sys::cass_future_get_error_result;
12use crate::cassandra_sys::CassErrorResult as CassErrorResult_;
13use crate::cassandra_sys::CassError_;
14use crate::cassandra_sys::CassFuture as _Future;
15use crate::cassandra_sys::CASS_OK;
16use crate::cassandra_sys::{
17    cass_error_num_arg_types, cass_error_result_arg_type, cass_error_result_consistency,
18    cass_error_result_data_present, cass_error_result_function, cass_error_result_keyspace,
19    cass_error_result_num_failures, cass_error_result_responses_received,
20    cass_error_result_responses_required, cass_error_result_table, cass_error_result_write_type,
21};
22use crate::cassandra_sys::{cass_false, cass_true};
23use crate::Session;
24
25use std::error::Error as IError;
26use std::ffi::{CStr, CString};
27use std::fmt::{Debug, Display, Formatter};
28use std::os::raw::c_char;
29use std::{fmt, mem, slice, str};
30
31// Define the errors that may be returned by this driver.
32error_chain! {
33    foreign_links {
34        StringContainsNul(::std::ffi::NulError)
35            #[doc = "Attempted to pass a string containing `\\0` to Cassandra"];
36
37        InvalidUtf8(::std::str::Utf8Error)
38            #[doc = "Attempted to decode an invalid UTF-8-encoded string"];
39    }
40
41    errors {
42        /// Cassandra error.
43        CassError(code: CassErrorCode, msg: String) {
44            description("Cassandra error")
45            display("Cassandra error {:?}: {}", &code, &msg)
46        }
47
48        /// Errors that happen when an invalid session is passed to a batch.
49        BatchSessionMismatch {
50            description("Batch cannot add a statement belonging to another session.")
51            display("Batch session mismatch")
52        }
53
54        /// Cassandra error result with extended information.
55        CassErrorResult(
56            code: CassErrorCode,
57            msg: String,
58            consistency: Consistency,
59            actual: i32,
60            required: i32,
61            num_failures: i32,
62            data_present: bool,
63            write_type: WriteType,
64            keyspace: Option<String>,
65            table: Option<String>,
66            function: Option<(String, Vec<String>)>
67        ) {
68            description("Cassandra detailed error")
69            display("Cassandra detailed error {:?}: {}", &code, &msg)
70        }
71
72        /// Unsupported type encountered.
73        UnsupportedType(expected: &'static str, actual: ValueType) {
74            description("Unsupported type")
75            display("Unsupported type {}; expected {}", actual, expected)
76        }
77
78    }
79}
80
81/// Extension trait for `CassError_`.
82pub(crate) trait CassErrorExt {
83    /// If this operation is successful, return `default`, otherwise an appropriate error.
84    fn to_result<T>(&self, default: T) -> Result<T>;
85
86    /// This is definitely an error - return it as such.
87    fn to_error(&self) -> Error;
88}
89
90impl CassErrorExt for CassError_ {
91    fn to_result<T>(&self, default: T) -> Result<T> {
92        unsafe {
93            match *self {
94                CASS_OK => Ok(default),
95                _ => {
96                    let message = CStr::from_ptr(cass_error_desc(*self))
97                        .to_string_lossy()
98                        .into_owned();
99                    Err(ErrorKind::CassError(CassErrorCode::build(*self), message).into())
100                }
101            }
102        }
103    }
104
105    fn to_error(&self) -> Error {
106        unsafe {
107            let message = CStr::from_ptr(cass_error_desc(*self))
108                .to_string_lossy()
109                .into_owned();
110            ErrorKind::CassError(CassErrorCode::build(*self), message).into()
111        }
112    }
113}
114
115impl CassErrorExt for CassErrorCode {
116    fn to_result<T>(&self, default: T) -> Result<T> {
117        self.inner().to_result(default)
118    }
119    fn to_error(&self) -> Error {
120        self.inner().to_error()
121    }
122}
123
124/// Build an error from the code, message, and optional `CassErrorResult_`.
125pub(crate) unsafe fn build_error_result(
126    code: CassErrorCode,
127    message: String,
128    e: *const CassErrorResult_,
129) -> Error {
130    if e.is_null() {
131        // No extended error available; just take the basic one.
132        ErrorKind::CassError(code, message).into()
133    } else {
134        // Get the extended error.
135        let consistency = Consistency::build(cass_error_result_consistency(e));
136        let actual = cass_error_result_responses_received(e);
137        // See https://datastax-oss.atlassian.net/browse/CPP-502 for these names.
138        // cassandra-sys uses the actual names and works around the header bug.
139        let required = cass_error_result_responses_required(e);
140        let num_failures = cass_error_result_num_failures(e);
141        let data_present = cass_error_result_data_present(e) != cass_false;
142        let write_type = WriteType::build(cass_error_result_write_type(e));
143        let keyspace = get_lossy_string(|s, s_len| cass_error_result_keyspace(e, s, s_len));
144        let table = get_lossy_string(|s, s_len| cass_error_result_table(e, s, s_len));
145        let function = get_lossy_string(|s, s_len| cass_error_result_function(e, s, s_len));
146        let function_call = function.map(|function| {
147            let i = cass_error_num_arg_types(e);
148            let mut args = vec![];
149            for i in 0..i {
150                let arg = get_lossy_string(|s, s_len| cass_error_result_arg_type(e, i, s, s_len))
151                    .unwrap_or("<error>".to_string());
152                args.push(arg);
153            }
154            (function, args)
155        });
156        cass_error_result_free(e);
157        ErrorKind::CassErrorResult(
158            code,
159            message,
160            consistency,
161            actual,
162            required,
163            num_failures,
164            data_present,
165            write_type,
166            keyspace,
167            table,
168            function_call,
169        )
170        .into()
171    }
172}
173
174/// Extract the error code and message from a Cassandra driver future
175pub(crate) unsafe fn get_cass_future_error(rc: CassError_, inner: *mut _Future) -> Error {
176    let code = CassErrorCode::build(rc);
177    let message = get_lossy_string(|s, s_len| {
178        cass_future_error_message(inner, s, s_len);
179        CASS_OK
180    })
181    .unwrap(); // always OK so cannot fail
182    build_error_result(code, message, cass_future_get_error_result(inner))
183}
184
185/// A Cassandra failure error code.
186#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
187#[allow(missing_docs)] // Meanings are defined in CQL documentation.
188#[allow(non_camel_case_types)] // Names are traditional.
189pub enum CassErrorCode {
190    // deliberately omits CASS_OK
191    LIB_BAD_PARAMS,
192    LIB_NO_STREAMS,
193    LIB_UNABLE_TO_INIT,
194    LIB_MESSAGE_ENCODE,
195    LIB_HOST_RESOLUTION,
196    LIB_UNEXPECTED_RESPONSE,
197    LIB_REQUEST_QUEUE_FULL,
198    LIB_NO_AVAILABLE_IO_THREAD,
199    LIB_WRITE_ERROR,
200    LIB_NO_HOSTS_AVAILABLE,
201    LIB_INDEX_OUT_OF_BOUNDS,
202    LIB_INVALID_ITEM_COUNT,
203    LIB_INVALID_VALUE_TYPE,
204    LIB_REQUEST_TIMED_OUT,
205    LIB_UNABLE_TO_SET_KEYSPACE,
206    LIB_CALLBACK_ALREADY_SET,
207    LIB_INVALID_STATEMENT_TYPE,
208    LIB_NAME_DOES_NOT_EXIST,
209    LIB_UNABLE_TO_DETERMINE_PROTOCOL,
210    LIB_NULL_VALUE,
211    LIB_NOT_IMPLEMENTED,
212    LIB_UNABLE_TO_CONNECT,
213    LIB_UNABLE_TO_CLOSE,
214    LIB_NO_PAGING_STATE,
215    LIB_PARAMETER_UNSET,
216    LIB_INVALID_ERROR_RESULT_TYPE,
217    LIB_INVALID_FUTURE_TYPE,
218    LIB_INTERNAL_ERROR,
219    LIB_INVALID_CUSTOM_TYPE,
220    LIB_INVALID_DATA,
221    LIB_NOT_ENOUGH_DATA,
222    LIB_INVALID_STATE,
223    LIB_NO_CUSTOM_PAYLOAD,
224    LIB_EXECUTION_PROFILE_INVALID,
225    SERVER_SERVER_ERROR,
226    SERVER_PROTOCOL_ERROR,
227    SERVER_BAD_CREDENTIALS,
228    SERVER_UNAVAILABLE,
229    SERVER_OVERLOADED,
230    SERVER_IS_BOOTSTRAPPING,
231    SERVER_TRUNCATE_ERROR,
232    SERVER_WRITE_TIMEOUT,
233    SERVER_READ_TIMEOUT,
234    SERVER_READ_FAILURE,
235    SERVER_FUNCTION_FAILURE,
236    SERVER_WRITE_FAILURE,
237    SERVER_SYNTAX_ERROR,
238    SERVER_UNAUTHORIZED,
239    SERVER_INVALID_QUERY,
240    SERVER_CONFIG_ERROR,
241    SERVER_ALREADY_EXISTS,
242    SERVER_UNPREPARED,
243    SSL_INVALID_CERT,
244    SSL_INVALID_PRIVATE_KEY,
245    SSL_NO_PEER_CERT,
246    SSL_INVALID_PEER_CERT,
247    SSL_IDENTITY_MISMATCH,
248    SSL_PROTOCOL_ERROR,
249    // deliberately omits LAST_ENTRY
250}
251
252enhance_nullary_enum!(CassErrorCode, CassError_, {
253    (LIB_BAD_PARAMS, CASS_ERROR_LIB_BAD_PARAMS, "LIB_BAD_PARAMS"),
254    (LIB_NO_STREAMS, CASS_ERROR_LIB_NO_STREAMS, "LIB_NO_STREAMS"),
255    (LIB_UNABLE_TO_INIT, CASS_ERROR_LIB_UNABLE_TO_INIT, "LIB_UNABLE_TO_INIT"),
256    (LIB_MESSAGE_ENCODE, CASS_ERROR_LIB_MESSAGE_ENCODE, "LIB_MESSAGE_ENCODE"),
257    (LIB_HOST_RESOLUTION, CASS_ERROR_LIB_HOST_RESOLUTION, "LIB_HOST_RESOLUTION"),
258    (LIB_UNEXPECTED_RESPONSE, CASS_ERROR_LIB_UNEXPECTED_RESPONSE, "LIB_UNEXPECTED_RESPONSE"),
259    (LIB_REQUEST_QUEUE_FULL, CASS_ERROR_LIB_REQUEST_QUEUE_FULL, "LIB_REQUEST_QUEUE_FULL"),
260    (LIB_NO_AVAILABLE_IO_THREAD, CASS_ERROR_LIB_NO_AVAILABLE_IO_THREAD, "LIB_NO_AVAILABLE_IO_THREAD"),
261    (LIB_WRITE_ERROR, CASS_ERROR_LIB_WRITE_ERROR, "LIB_WRITE_ERROR"),
262    (LIB_NO_HOSTS_AVAILABLE, CASS_ERROR_LIB_NO_HOSTS_AVAILABLE, "LIB_NO_HOSTS_AVAILABLE"),
263    (LIB_INDEX_OUT_OF_BOUNDS, CASS_ERROR_LIB_INDEX_OUT_OF_BOUNDS, "LIB_INDEX_OUT_OF_BOUNDS"),
264    (LIB_INVALID_ITEM_COUNT, CASS_ERROR_LIB_INVALID_ITEM_COUNT, "LIB_INVALID_ITEM_COUNT"),
265    (LIB_INVALID_VALUE_TYPE, CASS_ERROR_LIB_INVALID_VALUE_TYPE, "LIB_INVALID_VALUE_TYPE"),
266    (LIB_REQUEST_TIMED_OUT, CASS_ERROR_LIB_REQUEST_TIMED_OUT, "LIB_REQUEST_TIMED_OUT"),
267    (LIB_UNABLE_TO_SET_KEYSPACE, CASS_ERROR_LIB_UNABLE_TO_SET_KEYSPACE, "LIB_UNABLE_TO_SET_KEYSPACE"),
268    (LIB_CALLBACK_ALREADY_SET, CASS_ERROR_LIB_CALLBACK_ALREADY_SET, "LIB_CALLBACK_ALREADY_SET"),
269    (LIB_INVALID_STATEMENT_TYPE, CASS_ERROR_LIB_INVALID_STATEMENT_TYPE, "LIB_INVALID_STATEMENT_TYPE"),
270    (LIB_NAME_DOES_NOT_EXIST, CASS_ERROR_LIB_NAME_DOES_NOT_EXIST, "LIB_NAME_DOES_NOT_EXIST"),
271    (LIB_UNABLE_TO_DETERMINE_PROTOCOL, CASS_ERROR_LIB_UNABLE_TO_DETERMINE_PROTOCOL, "LIB_UNABLE_TO_DETERMINE_PROTOCOL"),
272    (LIB_NULL_VALUE, CASS_ERROR_LIB_NULL_VALUE, "LIB_NULL_VALUE"),
273    (LIB_NOT_IMPLEMENTED, CASS_ERROR_LIB_NOT_IMPLEMENTED, "LIB_NOT_IMPLEMENTED"),
274    (LIB_UNABLE_TO_CONNECT, CASS_ERROR_LIB_UNABLE_TO_CONNECT, "LIB_UNABLE_TO_CONNECT"),
275    (LIB_UNABLE_TO_CLOSE, CASS_ERROR_LIB_UNABLE_TO_CLOSE, "LIB_UNABLE_TO_CLOSE"),
276    (LIB_NO_PAGING_STATE, CASS_ERROR_LIB_NO_PAGING_STATE, "LIB_NO_PAGING_STATE"),
277    (LIB_PARAMETER_UNSET, CASS_ERROR_LIB_PARAMETER_UNSET, "LIB_PARAMETER_UNSET"),
278    (LIB_INVALID_ERROR_RESULT_TYPE, CASS_ERROR_LIB_INVALID_ERROR_RESULT_TYPE, "LIB_INVALID_ERROR_RESULT_TYPE"),
279    (LIB_INVALID_FUTURE_TYPE, CASS_ERROR_LIB_INVALID_FUTURE_TYPE, "LIB_INVALID_FUTURE_TYPE"),
280    (LIB_INTERNAL_ERROR, CASS_ERROR_LIB_INTERNAL_ERROR, "LIB_INTERNAL_ERROR"),
281    (LIB_INVALID_CUSTOM_TYPE, CASS_ERROR_LIB_INVALID_CUSTOM_TYPE, "LIB_INVALID_CUSTOM_TYPE"),
282    (LIB_INVALID_DATA, CASS_ERROR_LIB_INVALID_DATA, "LIB_INVALID_DATA"),
283    (LIB_NOT_ENOUGH_DATA, CASS_ERROR_LIB_NOT_ENOUGH_DATA, "LIB_NOT_ENOUGH_DATA"),
284    (LIB_INVALID_STATE, CASS_ERROR_LIB_INVALID_STATE, "LIB_INVALID_STATE"),
285    (LIB_NO_CUSTOM_PAYLOAD, CASS_ERROR_LIB_NO_CUSTOM_PAYLOAD, "LIB_NO_CUSTOM_PAYLOAD"),
286    (LIB_EXECUTION_PROFILE_INVALID, CASS_ERROR_LIB_EXECUTION_PROFILE_INVALID, "LIB_EXECUTION_PROFILE_INVALID"),
287    (SERVER_SERVER_ERROR, CASS_ERROR_SERVER_SERVER_ERROR, "SERVER_SERVER_ERROR"),
288    (SERVER_PROTOCOL_ERROR, CASS_ERROR_SERVER_PROTOCOL_ERROR, "SERVER_PROTOCOL_ERROR"),
289    (SERVER_BAD_CREDENTIALS, CASS_ERROR_SERVER_BAD_CREDENTIALS, "SERVER_BAD_CREDENTIALS"),
290    (SERVER_UNAVAILABLE, CASS_ERROR_SERVER_UNAVAILABLE, "SERVER_UNAVAILABLE"),
291    (SERVER_OVERLOADED, CASS_ERROR_SERVER_OVERLOADED, "SERVER_OVERLOADED"),
292    (SERVER_IS_BOOTSTRAPPING, CASS_ERROR_SERVER_IS_BOOTSTRAPPING, "SERVER_IS_BOOTSTRAPPING"),
293    (SERVER_TRUNCATE_ERROR, CASS_ERROR_SERVER_TRUNCATE_ERROR, "SERVER_TRUNCATE_ERROR"),
294    (SERVER_WRITE_TIMEOUT, CASS_ERROR_SERVER_WRITE_TIMEOUT, "SERVER_WRITE_TIMEOUT"),
295    (SERVER_READ_TIMEOUT, CASS_ERROR_SERVER_READ_TIMEOUT, "SERVER_READ_TIMEOUT"),
296    (SERVER_READ_FAILURE, CASS_ERROR_SERVER_READ_FAILURE, "SERVER_READ_FAILURE"),
297    (SERVER_FUNCTION_FAILURE, CASS_ERROR_SERVER_FUNCTION_FAILURE, "SERVER_FUNCTION_FAILURE"),
298    (SERVER_WRITE_FAILURE, CASS_ERROR_SERVER_WRITE_FAILURE, "SERVER_WRITE_FAILURE"),
299    (SERVER_SYNTAX_ERROR, CASS_ERROR_SERVER_SYNTAX_ERROR, "SERVER_SYNTAX_ERROR"),
300    (SERVER_UNAUTHORIZED, CASS_ERROR_SERVER_UNAUTHORIZED, "SERVER_UNAUTHORIZED"),
301    (SERVER_INVALID_QUERY, CASS_ERROR_SERVER_INVALID_QUERY, "SERVER_INVALID_QUERY"),
302    (SERVER_CONFIG_ERROR, CASS_ERROR_SERVER_CONFIG_ERROR, "SERVER_CONFIG_ERROR"),
303    (SERVER_ALREADY_EXISTS, CASS_ERROR_SERVER_ALREADY_EXISTS, "SERVER_ALREADY_EXISTS"),
304    (SERVER_UNPREPARED, CASS_ERROR_SERVER_UNPREPARED, "SERVER_UNPREPARED"),
305    (SSL_INVALID_CERT, CASS_ERROR_SSL_INVALID_CERT, "SSL_INVALID_CERT"),
306    (SSL_INVALID_PRIVATE_KEY, CASS_ERROR_SSL_INVALID_PRIVATE_KEY, "SSL_INVALID_PRIVATE_KEY"),
307    (SSL_NO_PEER_CERT, CASS_ERROR_SSL_NO_PEER_CERT, "SSL_NO_PEER_CERT"),
308    (SSL_INVALID_PEER_CERT, CASS_ERROR_SSL_INVALID_PEER_CERT, "SSL_INVALID_PEER_CERT"),
309    (SSL_IDENTITY_MISMATCH, CASS_ERROR_SSL_IDENTITY_MISMATCH, "SSL_IDENTITY_MISMATCH"),
310    (SSL_PROTOCOL_ERROR, CASS_ERROR_SSL_PROTOCOL_ERROR, "SSL_PROTOCOL_ERROR"),
311}, omit { CASS_OK, CASS_ERROR_LAST_ENTRY });
312
313/// Extract an optional C string lossily (i.e., using a replacement char for non-UTF-8 sequences).
314pub(crate) unsafe fn get_lossy_string<F>(get: F) -> Option<String>
315where
316    F: Fn(*mut *const ::std::os::raw::c_char, *mut usize) -> CassError_,
317{
318    let mut msg = mem::zeroed();
319    let mut msg_len = mem::zeroed();
320    match (get)(&mut msg, &mut msg_len) {
321        CASS_OK => (),
322        _ => return None,
323    }
324    let slice = slice::from_raw_parts(msg as *const u8, msg_len as usize);
325    Some(String::from_utf8_lossy(slice).into_owned())
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    pub fn test_conversion() {
334        assert_eq!(
335            CassErrorCode::build(CassError_::CASS_ERROR_SERVER_PROTOCOL_ERROR),
336            CassErrorCode::SERVER_PROTOCOL_ERROR
337        );
338        match CassErrorCode::LIB_INVALID_DATA.inner() {
339            CassError_::CASS_ERROR_LIB_INVALID_DATA => (),
340            e => panic!("Unexpected return value {:?}", e),
341        }
342    }
343
344    /// Test the enhance_nullary_enum! macro `omit` functionality works correctly.
345    #[test]
346    #[should_panic(expected = "Unexpected variant CassError_ :: CASS_OK")]
347    pub fn test_omitted_conversion_should_fail() {
348        CassErrorCode::build(CassError_::CASS_OK);
349    }
350}