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