tarantool/
error.rs

1//! Error handling utils.
2//!
3//! The Tarantool error handling works most like libc's errno. All API calls
4//! return -1 or `NULL` in the event of error. An internal pointer to
5//! `box_error_t` type is set by API functions to indicate what went wrong.
6//! This value is only significant if API call failed (returned -1 or `NULL`).
7//!
8//! Successful function can also touch the last error in some
9//! cases. You don't have to clear the last error before calling
10//! API functions. The returned object is valid only until next
11//! call to **any** API function.
12//!
13//! You must set the last error using `set_error()` in your stored C
14//! procedures if you want to return a custom error message.
15//! You can re-throw the last API error to IPROTO client by keeping
16//! the current value and returning -1 to Tarantool from your
17//! stored procedure.
18
19use std::collections::HashMap;
20use std::ffi::CStr;
21use std::fmt::{self, Display, Formatter};
22use std::io;
23use std::ptr::NonNull;
24use std::str::Utf8Error;
25use std::sync::Arc;
26
27use rmp::decode::{MarkerReadError, NumValueReadError, ValueReadError};
28use rmp::encode::ValueWriteError;
29
30use crate::ffi::tarantool as ffi;
31use crate::tlua::LuaError;
32use crate::transaction::TransactionError;
33use crate::util::to_cstring_lossy;
34
35/// A specialized [`Result`] type for the crate
36pub type Result<T, E = Error> = std::result::Result<T, E>;
37
38pub type TimeoutError<E> = crate::fiber::r#async::timeout::Error<E>;
39
40////////////////////////////////////////////////////////////////////////////////
41// Error
42////////////////////////////////////////////////////////////////////////////////
43
44/// Represents all error cases for all routines of crate (including Tarantool errors)
45#[derive(Debug, thiserror::Error)]
46#[non_exhaustive]
47pub enum Error {
48    #[error("box error: {0}")]
49    Tarantool(BoxError),
50
51    #[error("io error: {0}")]
52    IO(#[from] io::Error),
53
54    #[error("failed to encode tuple: {0}")]
55    Encode(#[from] EncodeError),
56
57    #[error("failed to decode tuple: {error} when decoding msgpack {} into rust type {expected_type}", crate::util::DisplayAsHexBytes(.actual_msgpack))]
58    Decode {
59        error: rmp_serde::decode::Error,
60        expected_type: String,
61        actual_msgpack: Vec<u8>,
62    },
63
64    #[error("failed to decode tuple: {0}")]
65    DecodeRmpValue(#[from] rmp_serde::decode::Error),
66
67    #[error("unicode string decode error: {0}")]
68    Unicode(#[from] Utf8Error),
69
70    #[error("numeric value read error: {0}")]
71    NumValueRead(#[from] NumValueReadError),
72
73    #[error("msgpack read error: {0}")]
74    ValueRead(#[from] ValueReadError),
75
76    #[error("msgpack write error: {0}")]
77    ValueWrite(#[from] ValueWriteError),
78
79    /// Error returned from the Tarantool server.
80    ///
81    /// It represents an error with which Tarantool server
82    /// answers to the client in case of faulty request or an error
83    /// during request execution on the server side.
84    #[error("server responded with error: {0}")]
85    Remote(BoxError),
86
87    #[error("{0}")]
88    Protocol(#[from] crate::network::protocol::ProtocolError),
89
90    /// The error is wrapped in a [`Arc`], because some libraries require
91    /// error types to implement [`Sync`], which isn't implemented for [`Rc`].
92    ///
93    /// [`Rc`]: std::rc::Rc
94    #[cfg(feature = "network_client")]
95    #[error("{0}")]
96    Tcp(Arc<crate::network::client::tcp::Error>),
97
98    #[error("lua error: {0}")]
99    LuaError(#[from] LuaError),
100
101    #[error("space metadata not found")]
102    MetaNotFound,
103
104    #[error("msgpack encode error: {0}")]
105    MsgpackEncode(#[from] crate::msgpack::EncodeError),
106
107    #[error("msgpack decode error: {0}")]
108    MsgpackDecode(#[from] crate::msgpack::DecodeError),
109
110    /// A network connection was closed for the given reason.
111    #[error("{0}")]
112    ConnectionClosed(Arc<Error>),
113
114    /// This should only be used if the error doesn't fall into one of the above
115    /// categories.
116    #[error("{0}")]
117    Other(Box<dyn std::error::Error + Send + Sync>),
118}
119
120const _: () = {
121    /// Assert Error implements Send + Sync
122    const fn if_this_compiles_the_type_implements_send_and_sync<T: Send + Sync>() {}
123    if_this_compiles_the_type_implements_send_and_sync::<Error>();
124};
125
126impl Error {
127    #[inline(always)]
128    pub fn other<E>(error: E) -> Self
129    where
130        E: Into<Box<dyn std::error::Error + Send + Sync>>,
131    {
132        Self::Other(error.into())
133    }
134
135    #[inline(always)]
136    pub fn decode<T>(error: rmp_serde::decode::Error, data: Vec<u8>) -> Self {
137        Error::Decode {
138            error,
139            expected_type: std::any::type_name::<T>().into(),
140            actual_msgpack: data,
141        }
142    }
143
144    /// Returns the name of the variant as it is spelled in the source code.
145    pub const fn variant_name(&self) -> &'static str {
146        match self {
147            Self::Tarantool(_) => "Box",
148            Self::IO(_) => "IO",
149            Self::Encode(_) => "Encode",
150            Self::Decode { .. } => "Decode",
151            Self::DecodeRmpValue(_) => "DecodeRmpValue",
152            Self::Unicode(_) => "Unicode",
153            Self::NumValueRead(_) => "NumValueRead",
154            Self::ValueRead(_) => "ValueRead",
155            Self::ValueWrite(_) => "ValueWrite",
156            Self::Remote(_) => "Remote",
157            Self::Protocol(_) => "Protocol",
158            #[cfg(feature = "network_client")]
159            Self::Tcp(_) => "Tcp",
160            Self::LuaError(_) => "LuaError",
161            Self::MetaNotFound => "MetaNotFound",
162            Self::MsgpackEncode(_) => "MsgpackEncode",
163            Self::MsgpackDecode(_) => "MsgpackDecode",
164            Self::ConnectionClosed(_) => "ConnectionClosed",
165            Self::Other(_) => "Other",
166        }
167    }
168}
169
170impl From<rmp_serde::encode::Error> for Error {
171    fn from(error: rmp_serde::encode::Error) -> Self {
172        EncodeError::from(error).into()
173    }
174}
175
176#[cfg(feature = "network_client")]
177impl From<crate::network::client::tcp::Error> for Error {
178    fn from(err: crate::network::client::tcp::Error) -> Self {
179        Error::Tcp(Arc::new(err))
180    }
181}
182
183impl From<MarkerReadError> for Error {
184    fn from(error: MarkerReadError) -> Self {
185        Error::ValueRead(error.into())
186    }
187}
188
189impl<E> From<TransactionError<E>> for Error
190where
191    Error: From<E>,
192{
193    #[inline]
194    #[track_caller]
195    fn from(e: TransactionError<E>) -> Self {
196        match e {
197            TransactionError::FailedToCommit(e) => e.into(),
198            TransactionError::FailedToRollback(e) => e.into(),
199            TransactionError::RolledBack(e) => e.into(),
200            TransactionError::AlreadyStarted => BoxError::new(
201                TarantoolErrorCode::ActiveTransaction,
202                "transaction has already been started",
203            )
204            .into(),
205        }
206    }
207}
208
209impl<E> From<TimeoutError<E>> for Error
210where
211    Error: From<E>,
212{
213    #[inline]
214    #[track_caller]
215    fn from(e: TimeoutError<E>) -> Self {
216        match e {
217            TimeoutError::Expired => BoxError::new(TarantoolErrorCode::Timeout, "timeout").into(),
218            TimeoutError::Failed(e) => e.into(),
219        }
220    }
221}
222
223impl From<std::string::FromUtf8Error> for Error {
224    #[inline(always)]
225    fn from(error: std::string::FromUtf8Error) -> Self {
226        // FIXME: we loose the data here
227        error.utf8_error().into()
228    }
229}
230
231////////////////////////////////////////////////////////////////////////////////
232// BoxError
233////////////////////////////////////////////////////////////////////////////////
234
235/// Structured info about an error which can happen as a result of an internal
236/// API or a remote procedure call.
237///
238/// Can also be used in user code to return structured error info from stored
239/// procedures.
240#[derive(Debug, Clone, Default)]
241pub struct BoxError {
242    pub(crate) code: u32,
243    pub(crate) message: Option<Box<str>>,
244    pub(crate) error_type: Option<Box<str>>,
245    pub(crate) errno: Option<u32>,
246    pub(crate) file: Option<Box<str>>,
247    pub(crate) line: Option<u32>,
248    pub(crate) fields: HashMap<Box<str>, rmpv::Value>,
249    pub(crate) cause: Option<Box<BoxError>>,
250}
251
252// TODO mark this as deprecated
253pub type TarantoolError = BoxError;
254
255impl BoxError {
256    /// Construct an error object with given error `code` and `message`. The
257    /// resulting error will have `file` & `line` fields set from the caller's
258    /// location.
259    ///
260    /// Use [`Self::with_location`] to override error location.
261    #[inline(always)]
262    #[track_caller]
263    pub fn new(code: impl Into<u32>, message: impl Into<String>) -> Self {
264        let location = std::panic::Location::caller();
265        Self {
266            code: code.into(),
267            message: Some(message.into().into_boxed_str()),
268            file: Some(location.file().into()),
269            line: Some(location.line()),
270            ..Default::default()
271        }
272    }
273
274    /// Construct an error object with given error `code` and `message` and
275    /// source location.
276    ///
277    /// Use [`Self::new`] to use the caller's location.
278    #[inline(always)]
279    pub fn with_location(
280        code: impl Into<u32>,
281        message: impl Into<String>,
282        file: impl Into<String>,
283        line: u32,
284    ) -> Self {
285        Self {
286            code: code.into(),
287            message: Some(message.into().into_boxed_str()),
288            file: Some(file.into().into_boxed_str()),
289            line: Some(line),
290            ..Default::default()
291        }
292    }
293
294    /// Tries to get the information about the last API call error. If error was not set
295    /// returns `Ok(())`
296    #[inline]
297    pub fn maybe_last() -> std::result::Result<(), Self> {
298        // This is safe as long as tarantool runtime is initialized
299        let error_ptr = unsafe { ffi::box_error_last() };
300        let Some(error_ptr) = NonNull::new(error_ptr) else {
301            return Ok(());
302        };
303
304        // This is safe, because box_error_last returns a valid pointer
305        Err(unsafe { Self::from_ptr(error_ptr) })
306    }
307
308    /// Create a `BoxError` from a poniter to the underlying struct.
309    ///
310    /// Use [`Self::maybe_last`] to automatically get the last error set by tarantool.
311    ///
312    /// # Safety
313    /// The pointer must point to a valid struct of type `BoxError`.
314    ///
315    /// Also must only be called from the `tx` thread.
316    pub unsafe fn from_ptr(error_ptr: NonNull<ffi::BoxError>) -> Self {
317        let code = ffi::box_error_code(error_ptr.as_ptr());
318
319        let message = CStr::from_ptr(ffi::box_error_message(error_ptr.as_ptr()));
320        let message = message.to_string_lossy().into_owned().into_boxed_str();
321
322        let error_type = CStr::from_ptr(ffi::box_error_type(error_ptr.as_ptr()));
323        let error_type = error_type.to_string_lossy().into_owned().into_boxed_str();
324
325        let mut file = None;
326        let mut line = None;
327        if let Some((f, l)) = error_get_file_line(error_ptr.as_ptr()) {
328            file = Some(f.into());
329            line = Some(l);
330        }
331
332        Self {
333            code,
334            message: Some(message),
335            error_type: Some(error_type),
336            errno: None,
337            file,
338            line,
339            fields: HashMap::default(),
340            cause: None,
341        }
342    }
343
344    /// Get the information about the last API call error.
345    #[inline(always)]
346    pub fn last() -> Self {
347        Self::maybe_last().err().unwrap()
348    }
349
350    /// Set `self` as the last API call error.
351    /// Useful when returning errors from stored prcoedures.
352    #[inline(always)]
353    #[track_caller]
354    pub fn set_last(&self) {
355        let mut loc = None;
356        if let Some(f) = self.file() {
357            debug_assert!(self.line().is_some());
358            loc = Some((f, self.line().unwrap_or(0)));
359        }
360        let message = to_cstring_lossy(self.message());
361        set_last_error(loc, self.error_code(), &message);
362    }
363
364    /// Return IPROTO error code
365    #[inline(always)]
366    pub fn error_code(&self) -> u32 {
367        self.code
368    }
369
370    /// Return the error type, e.g. "ClientError", "SocketError", etc.
371    #[inline(always)]
372    pub fn error_type(&self) -> &str {
373        self.error_type.as_deref().unwrap_or("Unknown")
374    }
375
376    /// Return the error message
377    #[inline(always)]
378    pub fn message(&self) -> &str {
379        self.message
380            .as_deref()
381            .unwrap_or("<error message is missing>")
382    }
383
384    /// Return the name of the source file where the error was created,
385    /// if it's available.
386    #[inline(always)]
387    pub fn file(&self) -> Option<&str> {
388        self.file.as_deref()
389    }
390
391    /// Return the source line number where the error was created,
392    /// if it's available.
393    #[inline(always)]
394    pub fn line(&self) -> Option<u32> {
395        self.line
396    }
397
398    /// Return the system `errno` value of the cause of this error,
399    /// if it's available.
400    ///
401    /// You can use [`std::io::Error::from_raw_os_error`] to get more details
402    /// for the returned error code.
403    #[inline(always)]
404    pub fn errno(&self) -> Option<u32> {
405        self.errno
406    }
407
408    /// Return the error which caused this one, if it's available.
409    #[inline(always)]
410    pub fn cause(&self) -> Option<&Self> {
411        self.cause.as_deref()
412    }
413
414    /// Return the map of additional fields.
415    #[inline(always)]
416    pub fn fields(&self) -> &HashMap<Box<str>, rmpv::Value> {
417        &self.fields
418    }
419}
420
421impl Display for BoxError {
422    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
423        if let Some(code) = TarantoolErrorCode::from_i64(self.code as _) {
424            return write!(f, "{:?}: {}", code, self.message());
425        }
426        write!(f, "box error #{}: {}", self.code, self.message())
427    }
428}
429
430impl From<BoxError> for Error {
431    fn from(error: BoxError) -> Self {
432        Error::Tarantool(error)
433    }
434}
435
436/// # Safety
437/// Only safe to be called from `tx` thread. Also `ptr` must point at a valid
438/// instance of `ffi::BoxError`.
439unsafe fn error_get_file_line(ptr: *const ffi::BoxError) -> Option<(String, u32)> {
440    #[derive(Clone, Copy)]
441    struct Failure;
442    static mut FIELD_OFFSETS: Option<std::result::Result<(u32, u32), Failure>> = None;
443
444    if (*std::ptr::addr_of!(FIELD_OFFSETS)).is_none() {
445        let lua = crate::lua_state();
446        let res = lua.eval::<(u32, u32)>(
447            "ffi = require 'ffi'
448            return
449                ffi.offsetof('struct error', '_file'),
450                ffi.offsetof('struct error', '_line')",
451        );
452        let (file_ofs, line_ofs) = crate::unwrap_ok_or!(res,
453            Err(e) => {
454                crate::say_warn!("failed getting struct error type info: {e}");
455                FIELD_OFFSETS = Some(Err(Failure));
456                return None;
457            }
458        );
459        FIELD_OFFSETS = Some(Ok((file_ofs, line_ofs)));
460    }
461    let (file_ofs, line_ofs) = crate::unwrap_ok_or!(
462        FIELD_OFFSETS.expect("always Some at this point"),
463        Err(Failure) => {
464            return None;
465        }
466    );
467
468    let ptr = ptr.cast::<u8>();
469    // TODO: check that struct error::_file is an array of bytes via lua-jit's ffi.typeinfo
470    let file_ptr = ptr.add(file_ofs as _).cast::<std::ffi::c_char>();
471    let file = CStr::from_ptr(file_ptr).to_string_lossy().into_owned();
472    // TODO: check that struct error::_line has type u32 via lua-jit's ffi.typeinfo
473    let line_ptr = ptr.add(line_ofs as _).cast::<u32>();
474    let line = *line_ptr;
475
476    Some((file, line))
477}
478
479/// Sets the last tarantool error. The `file_line` specifies source location to
480/// be set for the error. If it is `None`, the location of the caller is used
481/// (see [`std::panic::Location::caller`] for details on caller location).
482#[inline]
483#[track_caller]
484pub fn set_last_error(file_line: Option<(&str, u32)>, code: u32, message: &CStr) {
485    let (file, line) = crate::unwrap_or!(file_line, {
486        let file_line = std::panic::Location::caller();
487        (file_line.file(), file_line.line())
488    });
489
490    // XXX: we allocate memory each time this is called (sometimes even more
491    // than once). This is very sad...
492    let file = to_cstring_lossy(file);
493
494    // Safety: this is safe, because all pointers point to nul-terimnated
495    // strings, and the "%s" format works with any nul-terimnated string.
496    unsafe {
497        ffi::box_error_set(
498            file.as_ptr(),
499            line,
500            code,
501            crate::c_ptr!("%s"),
502            message.as_ptr(),
503        );
504    }
505}
506
507////////////////////////////////////////////////////////////////////////////////
508// IntoBoxError
509////////////////////////////////////////////////////////////////////////////////
510
511/// Types implementing this trait represent an error which can be converted to
512/// a structured tarantool internal error. In simple cases this may just be an
513/// conversion into an error message, but may also add an error code and/or
514/// additional custom fields. (custom fields not yet implemented).
515///
516/// All of the methods provide a default implementation for your convenience,
517/// so if you don't have do define them explicitly if you don't care about
518/// customizing the resulting `BoxError`'s fields. Your error type only needs
519/// to implement `Display` (which it most likely already implements).
520pub trait IntoBoxError: Sized + Display {
521    /// Set `self` as the current fiber's last error.
522    #[inline(always)]
523    #[track_caller]
524    fn set_last_error(self) {
525        self.into_box_error().set_last();
526    }
527
528    /// Convert `self` to `BoxError`.
529    #[track_caller]
530    #[inline(always)]
531    fn into_box_error(self) -> BoxError {
532        BoxError::new(self.error_code(), self.to_string())
533    }
534
535    /// Get the error code which would be used for the corresponding BoxError.
536    ///
537    /// For your convenience the default implementation is provided which
538    /// returns the `ER_PROC_C` error code (meaning the error originated from
539    /// a native stored procedure).
540    #[inline(always)]
541    fn error_code(&self) -> u32 {
542        TarantoolErrorCode::ProcC as _
543    }
544}
545
546impl IntoBoxError for BoxError {
547    #[inline(always)]
548    #[track_caller]
549    fn set_last_error(self) {
550        self.set_last()
551    }
552
553    #[inline(always)]
554    fn into_box_error(self) -> BoxError {
555        self
556    }
557
558    #[inline(always)]
559    fn error_code(&self) -> u32 {
560        self.error_code()
561    }
562}
563
564impl IntoBoxError for Error {
565    #[inline(always)]
566    #[track_caller]
567    fn into_box_error(self) -> BoxError {
568        let error_code = self.error_code();
569        match self {
570            Error::Tarantool(e) => e,
571            Error::Remote(e) => {
572                // TODO: maybe we want actually to set the last error to
573                // something like ProcC, "server responded with error" and then
574                // set `e` to be the `cause` of that error. But for now there's
575                // no way to do that
576                e
577            }
578            Error::Decode { .. } => BoxError::new(error_code, self.to_string()),
579            Error::DecodeRmpValue(e) => BoxError::new(error_code, e.to_string()),
580            Error::ValueRead(e) => BoxError::new(error_code, e.to_string()),
581            _ => BoxError::new(error_code, self.to_string()),
582        }
583    }
584
585    #[inline(always)]
586    fn error_code(&self) -> u32 {
587        match self {
588            Error::Tarantool(e) => e.error_code(),
589            Error::Remote(e) => e.error_code(),
590            Error::Decode { .. } => TarantoolErrorCode::InvalidMsgpack as _,
591            Error::DecodeRmpValue { .. } => TarantoolErrorCode::InvalidMsgpack as _,
592            Error::ValueRead { .. } => TarantoolErrorCode::InvalidMsgpack as _,
593            _ => TarantoolErrorCode::ProcC as _,
594        }
595    }
596}
597
598impl IntoBoxError for String {
599    #[track_caller]
600    fn into_box_error(self) -> BoxError {
601        BoxError::new(self.error_code(), self)
602    }
603
604    #[inline(always)]
605    fn error_code(&self) -> u32 {
606        TarantoolErrorCode::ProcC as _
607    }
608}
609
610impl IntoBoxError for &str {
611    #[inline(always)]
612    #[track_caller]
613    fn into_box_error(self) -> BoxError {
614        self.to_owned().into_box_error()
615    }
616
617    #[inline(always)]
618    fn error_code(&self) -> u32 {
619        TarantoolErrorCode::ProcC as _
620    }
621}
622
623#[cfg(feature = "anyhow")]
624impl IntoBoxError for anyhow::Error {
625    fn into_box_error(self) -> BoxError {
626        format!("{:#}", self).into_box_error()
627    }
628}
629
630impl IntoBoxError for Box<dyn std::error::Error> {
631    #[inline(always)]
632    #[track_caller]
633    fn into_box_error(self) -> BoxError {
634        (&*self).into_box_error()
635    }
636
637    #[inline(always)]
638    fn error_code(&self) -> u32 {
639        TarantoolErrorCode::ProcC as _
640    }
641}
642
643impl IntoBoxError for &dyn std::error::Error {
644    #[inline(always)]
645    #[track_caller]
646    fn into_box_error(self) -> BoxError {
647        let mut res = BoxError::new(self.error_code(), self.to_string());
648        if let Some(cause) = self.source() {
649            res.cause = Some(Box::new(cause.into_box_error()));
650        }
651        res
652    }
653
654    #[inline(always)]
655    fn error_code(&self) -> u32 {
656        TarantoolErrorCode::ProcC as _
657    }
658}
659
660////////////////////////////////////////////////////////////////////////////////
661// TarantoolErrorCode
662////////////////////////////////////////////////////////////////////////////////
663
664crate::define_enum_with_introspection! {
665    /// Codes of Tarantool errors
666    #[repr(u32)]
667    #[non_exhaustive]
668    pub enum TarantoolErrorCode {
669        Unknown = 0,
670        IllegalParams = 1,
671        MemoryIssue = 2,
672        TupleFound = 3,
673        TupleNotFound = 4,
674        Unsupported = 5,
675        NonMaster = 6,
676        Readonly = 7,
677        Injection = 8,
678        CreateSpace = 9,
679        SpaceExists = 10,
680        DropSpace = 11,
681        AlterSpace = 12,
682        IndexType = 13,
683        ModifyIndex = 14,
684        LastDrop = 15,
685        TupleFormatLimit = 16,
686        DropPrimaryKey = 17,
687        KeyPartType = 18,
688        ExactMatch = 19,
689        InvalidMsgpack = 20,
690        ProcRet = 21,
691        TupleNotArray = 22,
692        FieldType = 23,
693        IndexPartTypeMismatch = 24,
694        Splice = 25,
695        UpdateArgType = 26,
696        FormatMismatchIndexPart = 27,
697        UnknownUpdateOp = 28,
698        UpdateField = 29,
699        FunctionTxActive = 30,
700        KeyPartCount = 31,
701        ProcLua = 32,
702        NoSuchProc = 33,
703        NoSuchTrigger = 34,
704        NoSuchIndexID = 35,
705        NoSuchSpace = 36,
706        NoSuchFieldNo = 37,
707        ExactFieldCount = 38,
708        FieldMissing = 39,
709        WalIo = 40,
710        MoreThanOneTuple = 41,
711        AccessDenied = 42,
712        CreateUser = 43,
713        DropUser = 44,
714        NoSuchUser = 45,
715        UserExists = 46,
716        PasswordMismatch = 47,
717        UnknownRequestType = 48,
718        UnknownSchemaObject = 49,
719        CreateFunction = 50,
720        NoSuchFunction = 51,
721        FunctionExists = 52,
722        BeforeReplaceRet = 53,
723        MultistatementTransaction = 54,
724        TriggerExists = 55,
725        UserMax = 56,
726        NoSuchEngine = 57,
727        ReloadCfg = 58,
728        Cfg = 59,
729        SavepointEmptyTx = 60,
730        NoSuchSavepoint = 61,
731        UnknownReplica = 62,
732        ReplicasetUuidMismatch = 63,
733        InvalidUuid = 64,
734        ReplicasetUuidIsRo = 65,
735        InstanceUuidMismatch = 66,
736        ReplicaIDIsReserved = 67,
737        InvalidOrder = 68,
738        MissingRequestField = 69,
739        Identifier = 70,
740        DropFunction = 71,
741        IteratorType = 72,
742        ReplicaMax = 73,
743        InvalidXlog = 74,
744        InvalidXlogName = 75,
745        InvalidXlogOrder = 76,
746        NoConnection = 77,
747        Timeout = 78,
748        ActiveTransaction = 79,
749        CursorNoTransaction = 80,
750        CrossEngineTransaction = 81,
751        NoSuchRole = 82,
752        RoleExists = 83,
753        CreateRole = 84,
754        IndexExists = 85,
755        SessionClosed = 86,
756        RoleLoop = 87,
757        Grant = 88,
758        PrivGranted = 89,
759        RoleGranted = 90,
760        PrivNotGranted = 91,
761        RoleNotGranted = 92,
762        MissingSnapshot = 93,
763        CantUpdatePrimaryKey = 94,
764        UpdateIntegerOverflow = 95,
765        GuestUserPassword = 96,
766        TransactionConflict = 97,
767        UnsupportedPriv = 98,
768        LoadFunction = 99,
769        FunctionLanguage = 100,
770        RtreeRect = 101,
771        ProcC = 102,
772        UnknownRtreeIndexDistanceType = 103,
773        Protocol = 104,
774        UpsertUniqueSecondaryKey = 105,
775        WrongIndexRecord = 106,
776        WrongIndexParts = 107,
777        WrongIndexOptions = 108,
778        WrongSchemaVersion = 109,
779        MemtxMaxTupleSize = 110,
780        WrongSpaceOptions = 111,
781        UnsupportedIndexFeature = 112,
782        ViewIsRo = 113,
783        NoTransaction = 114,
784        System = 115,
785        Loading = 116,
786        ConnectionToSelf = 117,
787        KeyPartIsTooLong = 118,
788        Compression = 119,
789        CheckpointInProgress = 120,
790        SubStmtMax = 121,
791        CommitInSubStmt = 122,
792        RollbackInSubStmt = 123,
793        Decompression = 124,
794        InvalidXlogType = 125,
795        AlreadyRunning = 126,
796        IndexFieldCountLimit = 127,
797        LocalInstanceIDIsReadOnly = 128,
798        BackupInProgress = 129,
799        ReadViewAborted = 130,
800        InvalidIndexFile = 131,
801        InvalidRunFile = 132,
802        InvalidVylogFile = 133,
803        CheckpointRollback = 134,
804        VyQuotaTimeout = 135,
805        PartialKey = 136,
806        TruncateSystemSpace = 137,
807        LoadModule = 138,
808        VinylMaxTupleSize = 139,
809        WrongDdVersion = 140,
810        WrongSpaceFormat = 141,
811        CreateSequence = 142,
812        AlterSequence = 143,
813        DropSequence = 144,
814        NoSuchSequence = 145,
815        SequenceExists = 146,
816        SequenceOverflow = 147,
817        NoSuchIndexName = 148,
818        SpaceFieldIsDuplicate = 149,
819        CantCreateCollation = 150,
820        WrongCollationOptions = 151,
821        NullablePrimary = 152,
822        NoSuchFieldNameInSpace = 153,
823        TransactionYield = 154,
824        NoSuchGroup = 155,
825        SqlBindValue = 156,
826        SqlBindType = 157,
827        SqlBindParameterMax = 158,
828        SqlExecute = 159,
829        Unused = 160,
830        SqlBindNotFound = 161,
831        ActionMismatch = 162,
832        ViewMissingSql = 163,
833        ForeignKeyConstraint = 164,
834        NoSuchModule = 165,
835        NoSuchCollation = 166,
836        CreateFkConstraint = 167,
837        DropFkConstraint = 168,
838        NoSuchConstraint = 169,
839        ConstraintExists = 170,
840        SqlTypeMismatch = 171,
841        RowidOverflow = 172,
842        DropCollation = 173,
843        IllegalCollationMix = 174,
844        SqlNoSuchPragma = 175,
845        SqlCantResolveField = 176,
846        IndexExistsInSpace = 177,
847        InconsistentTypes = 178,
848        SqlSyntax = 179,
849        SqlStackOverflow = 180,
850        SqlSelectWildcard = 181,
851        SqlStatementEmpty = 182,
852        SqlKeywordIsReserved = 183,
853        SqlUnrecognizedSyntax = 184,
854        SqlUnknownToken = 185,
855        SqlParserGeneric = 186,
856        SqlAnalyzeArgument = 187,
857        SqlColumnCountMax = 188,
858        HexLiteralMax = 189,
859        IntLiteralMax = 190,
860        SqlParserLimit = 191,
861        IndexDefUnsupported = 192,
862        CkDefUnsupported = 193,
863        MultikeyIndexMismatch = 194,
864        CreateCkConstraint = 195,
865        CkConstraintFailed = 196,
866        SqlColumnCount = 197,
867        FuncIndexFunc = 198,
868        FuncIndexFormat = 199,
869        FuncIndexParts = 200,
870        NoSuchFieldNameInTuple = 201,
871        FuncWrongArgCount = 202,
872        BootstrapReadonly = 203,
873        SqlFuncWrongRetCount = 204,
874        FuncInvalidReturnType = 205,
875        SqlParserGenericWithPos = 206,
876        ReplicaNotAnon = 207,
877        CannotRegister = 208,
878        SessionSettingInvalidValue = 209,
879        SqlPrepare = 210,
880        WrongQueryId = 211,
881        SequenceNotStarted = 212,
882        NoSuchSessionSetting = 213,
883        UncommittedForeignSyncTxns = 214,
884        SyncMasterMismatch = 215,
885        SyncQuorumTimeout = 216,
886        SyncRollback = 217,
887        TupleMetadataIsTooBig = 218,
888        XlogGap = 219,
889        TooEarlySubscribe = 220,
890        SqlCantAddAutoinc = 221,
891        QuorumWait = 222,
892        InterferingPromote = 223,
893        ElectionDisabled = 224,
894        TxnRollback = 225,
895        NotLeader = 226,
896        SyncQueueUnclaimed = 227,
897        SyncQueueForeign = 228,
898        UnableToProcessInStream = 229,
899        UnableToProcessOutOfStream = 230,
900        TransactionTimeout = 231,
901        ActiveTimer = 232,
902        TupleFieldCountLimit = 233,
903        CreateConstraint = 234,
904        FieldConstraintFailed = 235,
905        TupleConstraintFailed = 236,
906        CreateForeignKey = 237,
907        ForeignKeyIntegrity = 238,
908        FieldForeignKeyFailed = 239,
909        ComplexForeignKeyFailed = 240,
910        WrongSpaceUpgradeOptions = 241,
911        NoElectionQuorum = 242,
912        Ssl = 243,
913        SplitBrain = 244,
914        OldTerm = 245,
915        InterferingElections = 246,
916        InvalidIteratorPosition = 247,
917        InvalidDefaultValueType = 248,
918        UnknownAuthMethod = 249,
919        InvalidAuthData = 250,
920        InvalidAuthRequest = 251,
921        WeakPassword = 252,
922        OldPassword = 253,
923        NoSuchSession = 254,
924        WrongSessionType = 255,
925        PasswordExpired = 256,
926        AuthDelay = 257,
927        AuthRequired = 258,
928        SqlSeqScan = 259,
929        NoSuchEvent = 260,
930        BootstrapNotUnanimous = 261,
931        CantCheckBootstrapLeader = 262,
932        BootstrapConnectionNotToAll = 263,
933        NilUuid = 264,
934        WrongFunctionOptions = 265,
935        MissingSystemSpaces = 266,
936        ExceededVdbeMaxSteps = 267,
937        IllegalOptions = 268,
938        IllegalOptionsFormat = 269,
939        CantGenerateUuid = 270,
940        SqlStatementBusy = 271,
941        SchemaUpdateInProgress = 272,
942        Unused7 = 273,
943        Unconfigured = 274,
944    }
945}
946
947#[allow(clippy::assertions_on_constants)]
948const _: () = {
949    assert!(TarantoolErrorCode::DISCRIMINANTS_ARE_SUBSEQUENT);
950};
951
952impl TarantoolErrorCode {
953    pub fn try_last() -> Option<Self> {
954        unsafe {
955            let e_ptr = ffi::box_error_last();
956            if e_ptr.is_null() {
957                return None;
958            }
959            let u32_code = ffi::box_error_code(e_ptr);
960            TarantoolErrorCode::from_i64(u32_code as _)
961        }
962    }
963
964    pub fn last() -> Self {
965        Self::try_last().unwrap()
966    }
967}
968
969impl From<TarantoolErrorCode> for u32 {
970    #[inline(always)]
971    fn from(code: TarantoolErrorCode) -> u32 {
972        code as _
973    }
974}
975
976////////////////////////////////////////////////////////////////////////////////
977// ...
978////////////////////////////////////////////////////////////////////////////////
979
980/// Clear the last error.
981pub fn clear_error() {
982    unsafe { ffi::box_error_clear() }
983}
984
985/// Set the last error.
986///
987/// # Example:
988/// ```rust
989/// # use tarantool::error::{TarantoolErrorCode, BoxError};
990/// # fn foo() -> Result<(), tarantool::error::BoxError> {
991/// let reason = "just 'cause";
992/// tarantool::set_error!(TarantoolErrorCode::Unsupported, "this you cannot do, because: {reason}");
993/// return Err(BoxError::last());
994/// # }
995/// ```
996#[macro_export]
997macro_rules! set_error {
998    ($code:expr, $($msg_args:tt)+) => {{
999        let msg = ::std::fmt::format(::std::format_args!($($msg_args)+));
1000        let msg = ::std::ffi::CString::new(msg).unwrap();
1001        $crate::error::set_last_error(None, $code as _, &msg);
1002    }};
1003}
1004
1005/// Set the last tarantool error and return it immediately.
1006///
1007/// # Example:
1008/// ```rust
1009/// # use tarantool::set_and_get_error;
1010/// # use tarantool::error::TarantoolErrorCode;
1011/// # fn foo() -> Result<(), tarantool::error::BoxError> {
1012/// let reason = "just 'cause";
1013/// return Err(set_and_get_error!(TarantoolErrorCode::Unsupported, "this you cannot do, because: {reason}"));
1014/// # }
1015/// ```
1016#[macro_export]
1017#[deprecated = "use `BoxError::new` instead"]
1018macro_rules! set_and_get_error {
1019    ($code:expr, $($msg_args:tt)+) => {{
1020        $crate::set_error!($code, $($msg_args)+);
1021        $crate::error::BoxError::last()
1022    }};
1023}
1024
1025////////////////////////////////////////////////////////////////////////////////
1026// EncodeError
1027////////////////////////////////////////////////////////////////////////////////
1028
1029#[deprecated = "use `EncodeError` instead"]
1030pub type Encode = EncodeError;
1031
1032/// Error that can happen when serializing a tuple
1033#[derive(Debug, thiserror::Error)]
1034pub enum EncodeError {
1035    #[error("{0}")]
1036    Rmp(#[from] rmp_serde::encode::Error),
1037
1038    #[error("invalid msgpack value (expected array, found {:?})", crate::util::DebugAsMPValue(.0))]
1039    InvalidMP(Vec<u8>),
1040}
1041
1042////////////////////////////////////////////////////////////////////////////////
1043// tests
1044////////////////////////////////////////////////////////////////////////////////
1045
1046#[test]
1047fn tarantool_error_doesnt_depend_on_link_error() {
1048    let err = Error::from(rmp_serde::decode::Error::OutOfRange);
1049    // This test checks that tarantool::error::Error can be displayed without
1050    // the need for linking to tarantool symbols, because `#[test]` tests are
1051    // linked into a standalone executable without access to those symbols.
1052    assert!(!err.to_string().is_empty());
1053    assert!(!format!("{}", err).is_empty());
1054}
1055
1056#[cfg(feature = "internal_test")]
1057mod tests {
1058    use super::*;
1059
1060    #[crate::test(tarantool = "crate")]
1061    fn set_error_expands_format() {
1062        let msg = "my message";
1063        set_error!(TarantoolErrorCode::Unknown, "{msg}");
1064        let e = BoxError::last();
1065        assert_eq!(e.to_string(), "Unknown: my message");
1066    }
1067
1068    #[crate::test(tarantool = "crate")]
1069    fn set_error_format_sequences() {
1070        for c in b'a'..=b'z' {
1071            let c = c as char;
1072            set_error!(TarantoolErrorCode::Unknown, "%{c}");
1073            let e = BoxError::last();
1074            assert_eq!(e.to_string(), format!("Unknown: %{c}"));
1075        }
1076    }
1077
1078    #[crate::test(tarantool = "crate")]
1079    fn set_error_caller_location() {
1080        //
1081        // If called in a function without `#[track_caller]`, the location of macro call is used
1082        //
1083        fn no_track_caller() {
1084            set_error!(TarantoolErrorCode::Unknown, "custom error");
1085        }
1086        let line_1 = line!() - 2; // line number where `set_error!` is called above
1087
1088        no_track_caller();
1089        let e = BoxError::last();
1090        assert_eq!(e.file(), Some(file!()));
1091        assert_eq!(e.line(), Some(line_1));
1092
1093        //
1094        // If called in a function with `#[track_caller]`, the location of the caller is used
1095        //
1096        #[track_caller]
1097        fn with_track_caller() {
1098            set_error!(TarantoolErrorCode::Unknown, "custom error");
1099        }
1100
1101        with_track_caller();
1102        let line_2 = line!() - 1; // line number where `with_track_caller()` is called above
1103
1104        let e = BoxError::last();
1105        assert_eq!(e.file(), Some(file!()));
1106        assert_eq!(e.line(), Some(line_2));
1107
1108        //
1109        // If specified explicitly, the provided values are used
1110        //
1111        set_last_error(
1112            Some(("foobar", 420)),
1113            69,
1114            crate::c_str!("super custom error"),
1115        );
1116        let e = BoxError::last();
1117        assert_eq!(e.file(), Some("foobar"));
1118        assert_eq!(e.line(), Some(420));
1119    }
1120
1121    #[crate::test(tarantool = "crate")]
1122    fn box_error_location() {
1123        //
1124        // If called in a function without `#[track_caller]`, the location where the error is constructed is used
1125        //
1126        fn no_track_caller() {
1127            let e = BoxError::new(69105_u32, "too many leaves");
1128            e.set_last();
1129        }
1130        let line_1 = line!() - 3; // line number where `BoxError` is constructed above
1131
1132        no_track_caller();
1133        let e = BoxError::last();
1134        assert_eq!(e.file(), Some(file!()));
1135        assert_eq!(e.line(), Some(line_1));
1136
1137        //
1138        // If called in a function with `#[track_caller]`, the location of the caller is used
1139        //
1140        #[track_caller]
1141        fn with_track_caller() {
1142            let e = BoxError::new(69105_u32, "too many leaves");
1143            e.set_last();
1144        }
1145
1146        with_track_caller();
1147        let line_2 = line!() - 1; // line number where `with_track_caller()` is called above
1148
1149        let e = BoxError::last();
1150        assert_eq!(e.file(), Some(file!()));
1151        assert_eq!(e.line(), Some(line_2));
1152
1153        //
1154        // If specified explicitly, the provided values are used
1155        //
1156        BoxError::with_location(69105_u32, "too many leaves", "nice", 69).set_last();
1157        let e = BoxError::last();
1158        assert_eq!(e.file(), Some("nice"));
1159        assert_eq!(e.line(), Some(69));
1160    }
1161
1162    #[crate::test(tarantool = "crate")]
1163    #[allow(clippy::let_unit_value)]
1164    fn set_error_with_no_semicolon() {
1165        // Basically you should always put double {{}} in your macros if there's
1166        // a let statement in it, otherwise it will suddenly stop compiling in
1167        // some weird context. And neither the compiler nor clippy will tell you
1168        // anything about this.
1169        () = set_error!(TarantoolErrorCode::Unknown, "idk");
1170
1171        if true {
1172            set_error!(TarantoolErrorCode::Unknown, "idk")
1173        } else {
1174            unreachable!()
1175        }; // <- Look at this beauty
1176           // Also never put ; after the if statement (unless it's required
1177           // for example if it's nested in a let statement), you should always
1178           // put ; inside both branches instead.
1179    }
1180
1181    #[crate::test(tarantool = "crate")]
1182    fn tarantool_error_use_after_free() {
1183        set_error!(TarantoolErrorCode::Unknown, "foo");
1184        let e = BoxError::last();
1185        assert_eq!(e.error_type(), "ClientError");
1186        clear_error();
1187        // This used to crash before the fix
1188        assert_eq!(e.error_type(), "ClientError");
1189    }
1190}