use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::io;
use std::ptr::NonNull;
use std::str::Utf8Error;
use std::sync::Arc;
use crate::ffi::tarantool as ffi;
use crate::tlua::LuaError;
use crate::transaction::TransactionError;
use crate::tuple::Decode;
use crate::util::to_cstring_lossy;
use rmp::decode::{MarkerReadError, NumValueReadError, ValueReadError};
use rmp::encode::ValueWriteError;
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub type TimeoutError<E> = crate::fiber::r#async::timeout::Error<E>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("box error: {0}")]
Tarantool(BoxError),
#[error("io error: {0}")]
IO(#[from] io::Error),
#[error("failed to encode tuple: {0}")]
Encode(#[from] EncodeError),
#[error("failed to decode tuple: {error} when decoding msgpack {} into rust type {expected_type}", crate::util::DisplayAsHexBytes(.actual_msgpack))]
Decode {
error: rmp_serde::decode::Error,
expected_type: String,
actual_msgpack: Vec<u8>,
},
#[error("failed to decode tuple: {0}")]
DecodeRmpValue(#[from] rmp_serde::decode::Error),
#[error("unicode string decode error: {0}")]
Unicode(#[from] Utf8Error),
#[error("numeric value read error: {0}")]
NumValueRead(#[from] NumValueReadError),
#[error("msgpack read error: {0}")]
ValueRead(#[from] ValueReadError),
#[error("msgpack write error: {0}")]
ValueWrite(#[from] ValueWriteError),
#[error("server responded with error: {0}")]
Remote(BoxError),
#[error("{0}")]
Protocol(#[from] crate::network::protocol::ProtocolError),
#[cfg(feature = "network_client")]
#[error("{0}")]
Tcp(Arc<crate::network::client::tcp::Error>),
#[error("lua error: {0}")]
LuaError(#[from] LuaError),
#[error("space metadata not found")]
MetaNotFound,
#[error("msgpack encode error: {0}")]
MsgpackEncode(#[from] crate::msgpack::EncodeError),
#[error("msgpack decode error: {0}")]
MsgpackDecode(#[from] crate::msgpack::DecodeError),
#[error("{0}")]
ConnectionClosed(Arc<Error>),
#[error("{0}")]
Other(Box<dyn std::error::Error + Send + Sync>),
}
const _: () = {
const fn if_this_compiles_the_type_implements_send_and_sync<T: Send + Sync>() {}
if_this_compiles_the_type_implements_send_and_sync::<Error>();
};
impl Error {
#[inline(always)]
pub fn other<E>(error: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Self::Other(error.into())
}
#[inline(always)]
pub fn decode<T>(error: rmp_serde::decode::Error, data: Vec<u8>) -> Self {
Error::Decode {
error,
expected_type: std::any::type_name::<T>().into(),
actual_msgpack: data,
}
}
pub const fn variant_name(&self) -> &'static str {
match self {
Self::Tarantool(_) => "Box",
Self::IO(_) => "IO",
Self::Encode(_) => "Encode",
Self::Decode { .. } => "Decode",
Self::DecodeRmpValue(_) => "DecodeRmpValue",
Self::Unicode(_) => "Unicode",
Self::NumValueRead(_) => "NumValueRead",
Self::ValueRead(_) => "ValueRead",
Self::ValueWrite(_) => "ValueWrite",
Self::Remote(_) => "Remote",
Self::Protocol(_) => "Protocol",
#[cfg(feature = "network_client")]
Self::Tcp(_) => "Tcp",
Self::LuaError(_) => "LuaError",
Self::MetaNotFound => "MetaNotFound",
Self::MsgpackEncode(_) => "MsgpackEncode",
Self::MsgpackDecode(_) => "MsgpackDecode",
Self::ConnectionClosed(_) => "ConnectionClosed",
Self::Other(_) => "Other",
}
}
}
impl From<rmp_serde::encode::Error> for Error {
fn from(error: rmp_serde::encode::Error) -> Self {
EncodeError::from(error).into()
}
}
#[cfg(feature = "network_client")]
impl From<crate::network::client::tcp::Error> for Error {
fn from(err: crate::network::client::tcp::Error) -> Self {
Error::Tcp(Arc::new(err))
}
}
impl From<MarkerReadError> for Error {
fn from(error: MarkerReadError) -> Self {
Error::ValueRead(error.into())
}
}
impl<E> From<TransactionError<E>> for Error
where
Error: From<E>,
{
#[inline]
#[track_caller]
fn from(e: TransactionError<E>) -> Self {
match e {
TransactionError::FailedToCommit(e) => e.into(),
TransactionError::FailedToRollback(e) => e.into(),
TransactionError::RolledBack(e) => e.into(),
TransactionError::AlreadyStarted => BoxError::new(
TarantoolErrorCode::ActiveTransaction,
"transaction has already been started",
)
.into(),
}
}
}
impl<E> From<TimeoutError<E>> for Error
where
Error: From<E>,
{
#[inline]
#[track_caller]
fn from(e: TimeoutError<E>) -> Self {
match e {
TimeoutError::Expired => BoxError::new(TarantoolErrorCode::Timeout, "timeout").into(),
TimeoutError::Failed(e) => e.into(),
}
}
}
impl From<std::string::FromUtf8Error> for Error {
#[inline(always)]
fn from(error: std::string::FromUtf8Error) -> Self {
error.utf8_error().into()
}
}
#[derive(Debug, Clone, Default)]
pub struct BoxError {
pub(crate) code: u32,
pub(crate) message: Option<Box<str>>,
pub(crate) error_type: Option<Box<str>>,
pub(crate) errno: Option<u32>,
pub(crate) file: Option<Box<str>>,
pub(crate) line: Option<u32>,
pub(crate) fields: HashMap<Box<str>, rmpv::Value>,
pub(crate) cause: Option<Box<BoxError>>,
}
pub type TarantoolError = BoxError;
impl BoxError {
#[inline(always)]
#[track_caller]
pub fn new(code: impl Into<u32>, message: impl Into<String>) -> Self {
let location = std::panic::Location::caller();
Self {
code: code.into(),
message: Some(message.into().into_boxed_str()),
file: Some(location.file().into()),
line: Some(location.line()),
..Default::default()
}
}
#[inline(always)]
pub fn with_location(
code: impl Into<u32>,
message: impl Into<String>,
file: impl Into<String>,
line: u32,
) -> Self {
Self {
code: code.into(),
message: Some(message.into().into_boxed_str()),
file: Some(file.into().into_boxed_str()),
line: Some(line),
..Default::default()
}
}
#[inline]
pub fn maybe_last() -> std::result::Result<(), Self> {
let error_ptr = unsafe { ffi::box_error_last() };
let Some(error_ptr) = NonNull::new(error_ptr) else {
return Ok(());
};
Err(unsafe { Self::from_ptr(error_ptr) })
}
pub unsafe fn from_ptr(error_ptr: NonNull<ffi::BoxError>) -> Self {
let code = ffi::box_error_code(error_ptr.as_ptr());
let message = CStr::from_ptr(ffi::box_error_message(error_ptr.as_ptr()));
let message = message.to_string_lossy().into_owned().into_boxed_str();
let error_type = CStr::from_ptr(ffi::box_error_type(error_ptr.as_ptr()));
let error_type = error_type.to_string_lossy().into_owned().into_boxed_str();
let mut file = None;
let mut line = None;
if let Some((f, l)) = error_get_file_line(error_ptr.as_ptr()) {
file = Some(f.into());
line = Some(l);
}
#[cfg(feature = "picodata")]
let fields = unsafe { get_error_fields(error_ptr.as_ptr()) };
#[cfg(not(feature = "picodata"))]
let fields = HashMap::new();
Self {
code,
message: Some(message),
error_type: Some(error_type),
errno: None,
file,
line,
fields,
cause: None,
}
}
#[inline(always)]
pub fn last() -> Self {
Self::maybe_last().err().unwrap()
}
#[inline(always)]
#[track_caller]
pub fn set_last(&self) {
let mut loc = None;
if let Some(f) = self.file() {
debug_assert!(self.line().is_some());
loc = Some((f, self.line().unwrap_or(0)));
}
let message = to_cstring_lossy(self.message());
set_last_error(loc, self.error_code(), &message);
#[cfg(feature = "picodata")]
if !self.fields.is_empty() {
set_last_error_fields(&self.fields);
}
}
#[inline(always)]
pub fn set_field(&mut self, field: &str, value: rmpv::Value) {
self.fields.insert(field.into(), value);
}
#[inline(always)]
pub fn error_code(&self) -> u32 {
self.code
}
#[inline(always)]
pub fn error_type(&self) -> &str {
self.error_type.as_deref().unwrap_or("Unknown")
}
#[inline(always)]
pub fn message(&self) -> &str {
self.message
.as_deref()
.unwrap_or("<error message is missing>")
}
#[inline(always)]
pub fn file(&self) -> Option<&str> {
self.file.as_deref()
}
#[inline(always)]
pub fn line(&self) -> Option<u32> {
self.line
}
#[inline(always)]
pub fn errno(&self) -> Option<u32> {
self.errno
}
#[inline(always)]
pub fn cause(&self) -> Option<&Self> {
self.cause.as_deref()
}
#[inline(always)]
pub fn fields(&self) -> &HashMap<Box<str>, rmpv::Value> {
&self.fields
}
#[inline(always)]
pub fn field(&self, field: &str) -> Option<&rmpv::Value> {
self.fields.get(field)
}
pub fn set_display_fallback(
cb: impl Fn(&Self, &mut Formatter<'_>) -> Result<bool, std::fmt::Error> + 'static,
) -> Result<(), BoxError> {
BOX_ERROR_FALLBACK_DISPLAY.with(|fallback| {
if fallback.get().is_some() {
return Err(BoxError::new(
TarantoolErrorCode::Unsupported,
"fallback callback is already set, cannot change it",
));
}
if fallback.set(Box::new(cb)).is_err() {
unreachable!("already checked it's empty");
}
Ok(())
})
}
}
impl<'a> From<&'a BoxError> for std::borrow::Cow<'a, BoxError> {
#[inline]
fn from(e: &'a BoxError) -> Self {
Self::Borrowed(e)
}
}
impl From<BoxError> for std::borrow::Cow<'_, BoxError> {
#[inline]
fn from(e: BoxError) -> Self {
Self::Owned(e)
}
}
impl Display for BoxError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if matches!(&self.error_type, Some(s) if &**s == "ShardingError") {
return write!(f, "ShardingError #{}: {}", self.code, self.message());
}
if let Some(code) = TarantoolErrorCode::from_i64(self.code as _) {
return write!(f, "{:?}: {}", code, self.message());
}
let res = BOX_ERROR_FALLBACK_DISPLAY.with(|fallback| fallback.get().map(|cb| cb(self, f)));
if let Some(res) = res {
if res? {
return Ok(());
}
}
write!(f, "box error #{}: {}", self.code, self.message())
}
}
std::thread_local! {
static BOX_ERROR_FALLBACK_DISPLAY: std::cell::OnceCell<BoxErrorFallbackDisplayImpl> = std::cell::OnceCell::new();
}
type BoxErrorFallbackDisplayImpl =
Box<dyn Fn(&BoxError, &mut Formatter<'_>) -> Result<bool, std::fmt::Error>>;
impl From<BoxError> for Error {
fn from(error: BoxError) -> Self {
Error::Tarantool(error)
}
}
impl std::error::Error for BoxError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.cause.as_ref().map(|e| e as &dyn std::error::Error)
}
}
impl<L> tlua::LuaRead<L> for BoxError
where
L: tlua::AsLua,
{
fn lua_read_at_position(lua: L, index: std::num::NonZeroI32) -> tlua::ReadResult<Self, L> {
let l = lua.as_lua();
let i = index.into();
if unsafe { crate::ffi::has_box_error_from_lua() } {
let ptr = unsafe { crate::ffi::tarantool::luaL_iserror(l, i) };
if let Some(ptr) = std::ptr::NonNull::new(ptr) {
let res = unsafe { Self::from_ptr(ptr) };
return Ok(res);
}
} else {
}
let ty = unsafe { tlua::ffi::lua_type(l, i) };
match ty {
tlua::ffi::LUA_TSTRING => {
let mut len = 0;
let ptr = unsafe { tlua::ffi::lua_tolstring(l, i, &mut len) };
let mut s = std::borrow::Cow::Borrowed("<unexpected error>");
if !ptr.is_null() {
let bytes = unsafe { std::slice::from_raw_parts(ptr as _, len as _) };
s = String::from_utf8_lossy(bytes);
}
return Ok(Self::new(TarantoolErrorCode::ProcLua, s));
}
tlua::ffi::LUA_TTABLE => {
let t = tlua::LuaTable::lua_read(lua)?;
let error_type = match t.try_get::<_, String>("type") {
Ok(v) => v,
Err(e) => {
let e = to_wrong_type(e).expected("field 'type' of type string");
return Err((t.into_inner(), e));
}
};
let code = match t.try_get::<_, u32>("code") {
Ok(v) => v,
Err(e) => {
let e = to_wrong_type(e).expected("field 'code' of type u32");
return Err((t.into_inner(), e));
}
};
let message = match t.try_get::<_, String>("message") {
Ok(v) => v,
Err(e) => {
let e = to_wrong_type(e).expected("field 'message' of type string");
return Err((t.into_inner(), e));
}
};
let mut res = Self::new(code, message);
res.error_type = Some(error_type.into());
return Ok(res);
}
_ => {}
}
let e = tlua::WrongType::info("decoding BoxError")
.expected_type::<Self>()
.actual_single_lua(l, index);
return Err((lua, e));
fn to_wrong_type(e: tlua::LuaError) -> tlua::WrongType {
match e {
tlua::LuaError::WrongType(e) => e,
other_error => {
tlua::WrongType::info("").actual(format!("thrown error: {other_error}"))
}
}
.when("decoding error object")
}
}
}
unsafe fn error_get_file_line(ptr: *const ffi::BoxError) -> Option<(String, u32)> {
#[derive(Clone, Copy)]
struct Failure;
static mut FIELD_OFFSETS: Option<std::result::Result<(u32, u32), Failure>> = None;
if (*std::ptr::addr_of!(FIELD_OFFSETS)).is_none() {
let lua = crate::lua_state();
let res = lua.eval::<(u32, u32)>(
"ffi = require 'ffi'
return
ffi.offsetof('struct error', '_file'),
ffi.offsetof('struct error', '_line')",
);
let (file_ofs, line_ofs) = crate::unwrap_ok_or!(res,
Err(e) => {
crate::say_warn!("failed getting struct error type info: {e}");
FIELD_OFFSETS = Some(Err(Failure));
return None;
}
);
FIELD_OFFSETS = Some(Ok((file_ofs, line_ofs)));
}
let (file_ofs, line_ofs) = crate::unwrap_ok_or!(
FIELD_OFFSETS.expect("always Some at this point"),
Err(Failure) => {
return None;
}
);
let ptr = ptr.cast::<u8>();
let file_ptr = ptr.add(file_ofs as _).cast::<std::ffi::c_char>();
let file = CStr::from_ptr(file_ptr).to_string_lossy().into_owned();
let line_ptr = ptr.add(line_ofs as _).cast::<u32>();
let line = *line_ptr;
Some((file, line))
}
#[inline]
#[track_caller]
pub fn set_last_error(file_line: Option<(&str, u32)>, code: u32, message: &CStr) {
let (file, line) = crate::unwrap_or!(file_line, {
let file_line = std::panic::Location::caller();
(file_line.file(), file_line.line())
});
let file = to_cstring_lossy(file);
unsafe {
ffi::box_error_set(
file.as_ptr(),
line,
code,
crate::c_ptr!("%s"),
message.as_ptr(),
);
}
}
#[cfg(feature = "picodata")]
unsafe fn get_error_fields(error: *const ffi::BoxError) -> HashMap<Box<str>, rmpv::Value> {
let payload = unsafe { ffi::error_get_payload(error as *mut ffi::BoxError) }
as *const ffi::BoxErrorPayload;
let mut result = HashMap::new();
let mut iter = ffi::BoxErrorPayloadIter {
next_position: 0,
..Default::default()
};
while unsafe { ffi::error_payload_iter_next(payload, &mut iter) } {
let name_cstr = unsafe { CStr::from_ptr(iter.name) };
let Ok(name_str) = name_cstr.to_str() else {
continue;
};
let name_str: Box<str> = name_str.into();
let data_slice =
unsafe { std::slice::from_raw_parts(iter.mp_value as *const u8, iter.mp_size) };
let Ok(data_value) = rmpv::Value::decode(data_slice) else {
continue;
};
result.insert(name_str, data_value);
}
result
}
#[cfg(feature = "picodata")]
unsafe fn set_error_fields(error: *mut ffi::BoxError, fields: &HashMap<Box<str>, rmpv::Value>) {
let payload = unsafe { ffi::error_get_payload(error) };
let mut encoded = Vec::new();
for (name, value) in fields {
let name = to_cstring_lossy(name);
encoded.clear();
rmpv::encode::write_value(&mut encoded, value).unwrap();
unsafe {
crate::ffi::tarantool::error_payload_set_mp(
payload,
name.as_ptr(),
encoded.as_ptr() as *const std::ffi::c_char,
encoded.len() as u32,
)
};
}
}
#[cfg(feature = "picodata")]
#[inline]
pub fn set_last_error_fields(fields: &HashMap<Box<str>, rmpv::Value>) {
{
let error = unsafe { ffi::box_error_last() };
assert_ne!(
error,
std::ptr::null_mut(),
"Attempt to set fields of the last tarantool error while no error was set"
);
unsafe { set_error_fields(error, fields) };
}
}
pub trait IntoBoxError: Sized + Display {
#[inline(always)]
#[track_caller]
fn set_last_error(self) {
self.into_box_error().set_last();
}
#[track_caller]
#[inline(always)]
fn into_box_error(self) -> BoxError {
BoxError::new(self.error_code(), self.to_string())
}
#[inline(always)]
fn error_code(&self) -> u32 {
TarantoolErrorCode::ProcC as _
}
}
impl IntoBoxError for BoxError {
#[inline(always)]
#[track_caller]
fn set_last_error(self) {
self.set_last()
}
#[inline(always)]
fn into_box_error(self) -> BoxError {
self
}
#[inline(always)]
fn error_code(&self) -> u32 {
self.error_code()
}
}
impl IntoBoxError for Error {
#[inline(always)]
#[track_caller]
fn into_box_error(self) -> BoxError {
let error_code = self.error_code();
match self {
Error::Tarantool(e) => e,
Error::Remote(e) => {
e
}
Error::Decode { .. } => BoxError::new(error_code, self.to_string()),
Error::DecodeRmpValue(e) => BoxError::new(error_code, e.to_string()),
Error::ValueRead(e) => BoxError::new(error_code, e.to_string()),
_ => BoxError::new(error_code, self.to_string()),
}
}
#[inline(always)]
fn error_code(&self) -> u32 {
match self {
Error::Tarantool(e) => e.error_code(),
Error::Remote(e) => e.error_code(),
Error::Decode { .. } => TarantoolErrorCode::InvalidMsgpack as _,
Error::DecodeRmpValue { .. } => TarantoolErrorCode::InvalidMsgpack as _,
Error::ValueRead { .. } => TarantoolErrorCode::InvalidMsgpack as _,
_ => TarantoolErrorCode::ProcC as _,
}
}
}
impl IntoBoxError for String {
#[track_caller]
fn into_box_error(self) -> BoxError {
BoxError::new(self.error_code(), self)
}
#[inline(always)]
fn error_code(&self) -> u32 {
TarantoolErrorCode::ProcC as _
}
}
impl IntoBoxError for &str {
#[inline(always)]
#[track_caller]
fn into_box_error(self) -> BoxError {
self.to_owned().into_box_error()
}
#[inline(always)]
fn error_code(&self) -> u32 {
TarantoolErrorCode::ProcC as _
}
}
#[cfg(feature = "anyhow")]
impl IntoBoxError for anyhow::Error {
fn into_box_error(self) -> BoxError {
format!("{:#}", self).into_box_error()
}
}
impl IntoBoxError for Box<dyn std::error::Error> {
#[inline(always)]
#[track_caller]
fn into_box_error(self) -> BoxError {
(&*self).into_box_error()
}
#[inline(always)]
fn error_code(&self) -> u32 {
TarantoolErrorCode::ProcC as _
}
}
impl IntoBoxError for &dyn std::error::Error {
#[inline(always)]
#[track_caller]
fn into_box_error(self) -> BoxError {
let mut res = BoxError::new(self.error_code(), self.to_string());
if let Some(cause) = self.source() {
res.cause = Some(Box::new(cause.into_box_error()));
}
res
}
#[inline(always)]
fn error_code(&self) -> u32 {
TarantoolErrorCode::ProcC as _
}
}
crate::define_enum_with_introspection! {
#[repr(u32)]
#[non_exhaustive]
pub enum TarantoolErrorCode {
Unknown = 0,
IllegalParams = 1,
MemoryIssue = 2,
TupleFound = 3,
TupleNotFound = 4,
Unsupported = 5,
NonMaster = 6,
Readonly = 7,
Injection = 8,
CreateSpace = 9,
SpaceExists = 10,
DropSpace = 11,
AlterSpace = 12,
IndexType = 13,
ModifyIndex = 14,
LastDrop = 15,
TupleFormatLimit = 16,
DropPrimaryKey = 17,
KeyPartType = 18,
ExactMatch = 19,
InvalidMsgpack = 20,
ProcRet = 21,
TupleNotArray = 22,
FieldType = 23,
IndexPartTypeMismatch = 24,
Splice = 25,
UpdateArgType = 26,
FormatMismatchIndexPart = 27,
UnknownUpdateOp = 28,
UpdateField = 29,
FunctionTxActive = 30,
KeyPartCount = 31,
ProcLua = 32,
NoSuchProc = 33,
NoSuchTrigger = 34,
NoSuchIndexID = 35,
NoSuchSpace = 36,
NoSuchFieldNo = 37,
ExactFieldCount = 38,
FieldMissing = 39,
WalIo = 40,
MoreThanOneTuple = 41,
AccessDenied = 42,
CreateUser = 43,
DropUser = 44,
NoSuchUser = 45,
UserExists = 46,
PasswordMismatch = 47,
UnknownRequestType = 48,
UnknownSchemaObject = 49,
CreateFunction = 50,
NoSuchFunction = 51,
FunctionExists = 52,
BeforeReplaceRet = 53,
MultistatementTransaction = 54,
TriggerExists = 55,
UserMax = 56,
NoSuchEngine = 57,
ReloadCfg = 58,
Cfg = 59,
SavepointEmptyTx = 60,
NoSuchSavepoint = 61,
UnknownReplica = 62,
ReplicasetUuidMismatch = 63,
InvalidUuid = 64,
ReplicasetUuidIsRo = 65,
InstanceUuidMismatch = 66,
ReplicaIDIsReserved = 67,
InvalidOrder = 68,
MissingRequestField = 69,
Identifier = 70,
DropFunction = 71,
IteratorType = 72,
ReplicaMax = 73,
InvalidXlog = 74,
InvalidXlogName = 75,
InvalidXlogOrder = 76,
NoConnection = 77,
Timeout = 78,
ActiveTransaction = 79,
CursorNoTransaction = 80,
CrossEngineTransaction = 81,
NoSuchRole = 82,
RoleExists = 83,
CreateRole = 84,
IndexExists = 85,
SessionClosed = 86,
RoleLoop = 87,
Grant = 88,
PrivGranted = 89,
RoleGranted = 90,
PrivNotGranted = 91,
RoleNotGranted = 92,
MissingSnapshot = 93,
CantUpdatePrimaryKey = 94,
UpdateIntegerOverflow = 95,
GuestUserPassword = 96,
TransactionConflict = 97,
UnsupportedPriv = 98,
LoadFunction = 99,
FunctionLanguage = 100,
RtreeRect = 101,
ProcC = 102,
UnknownRtreeIndexDistanceType = 103,
Protocol = 104,
UpsertUniqueSecondaryKey = 105,
WrongIndexRecord = 106,
WrongIndexParts = 107,
WrongIndexOptions = 108,
WrongSchemaVersion = 109,
MemtxMaxTupleSize = 110,
WrongSpaceOptions = 111,
UnsupportedIndexFeature = 112,
ViewIsRo = 113,
NoTransaction = 114,
System = 115,
Loading = 116,
ConnectionToSelf = 117,
KeyPartIsTooLong = 118,
Compression = 119,
CheckpointInProgress = 120,
SubStmtMax = 121,
CommitInSubStmt = 122,
RollbackInSubStmt = 123,
Decompression = 124,
InvalidXlogType = 125,
AlreadyRunning = 126,
IndexFieldCountLimit = 127,
LocalInstanceIDIsReadOnly = 128,
BackupInProgress = 129,
ReadViewAborted = 130,
InvalidIndexFile = 131,
InvalidRunFile = 132,
InvalidVylogFile = 133,
CheckpointRollback = 134,
VyQuotaTimeout = 135,
PartialKey = 136,
TruncateSystemSpace = 137,
LoadModule = 138,
VinylMaxTupleSize = 139,
WrongDdVersion = 140,
WrongSpaceFormat = 141,
CreateSequence = 142,
AlterSequence = 143,
DropSequence = 144,
NoSuchSequence = 145,
SequenceExists = 146,
SequenceOverflow = 147,
NoSuchIndexName = 148,
SpaceFieldIsDuplicate = 149,
CantCreateCollation = 150,
WrongCollationOptions = 151,
NullablePrimary = 152,
NoSuchFieldNameInSpace = 153,
TransactionYield = 154,
NoSuchGroup = 155,
SqlBindValue = 156,
SqlBindType = 157,
SqlBindParameterMax = 158,
SqlExecute = 159,
Unused = 160,
SqlBindNotFound = 161,
ActionMismatch = 162,
ViewMissingSql = 163,
ForeignKeyConstraint = 164,
NoSuchModule = 165,
NoSuchCollation = 166,
CreateFkConstraint = 167,
DropFkConstraint = 168,
NoSuchConstraint = 169,
ConstraintExists = 170,
SqlTypeMismatch = 171,
RowidOverflow = 172,
DropCollation = 173,
IllegalCollationMix = 174,
SqlNoSuchPragma = 175,
SqlCantResolveField = 176,
IndexExistsInSpace = 177,
InconsistentTypes = 178,
SqlSyntax = 179,
SqlStackOverflow = 180,
SqlSelectWildcard = 181,
SqlStatementEmpty = 182,
SqlKeywordIsReserved = 183,
SqlUnrecognizedSyntax = 184,
SqlUnknownToken = 185,
SqlParserGeneric = 186,
SqlAnalyzeArgument = 187,
SqlColumnCountMax = 188,
HexLiteralMax = 189,
IntLiteralMax = 190,
SqlParserLimit = 191,
IndexDefUnsupported = 192,
CkDefUnsupported = 193,
MultikeyIndexMismatch = 194,
CreateCkConstraint = 195,
CkConstraintFailed = 196,
SqlColumnCount = 197,
FuncIndexFunc = 198,
FuncIndexFormat = 199,
FuncIndexParts = 200,
NoSuchFieldNameInTuple = 201,
FuncWrongArgCount = 202,
BootstrapReadonly = 203,
SqlFuncWrongRetCount = 204,
FuncInvalidReturnType = 205,
SqlParserGenericWithPos = 206,
ReplicaNotAnon = 207,
CannotRegister = 208,
SessionSettingInvalidValue = 209,
SqlPrepare = 210,
WrongQueryId = 211,
SequenceNotStarted = 212,
NoSuchSessionSetting = 213,
UncommittedForeignSyncTxns = 214,
SyncMasterMismatch = 215,
SyncQuorumTimeout = 216,
SyncRollback = 217,
TupleMetadataIsTooBig = 218,
XlogGap = 219,
TooEarlySubscribe = 220,
SqlCantAddAutoinc = 221,
QuorumWait = 222,
InterferingPromote = 223,
ElectionDisabled = 224,
TxnRollback = 225,
NotLeader = 226,
SyncQueueUnclaimed = 227,
SyncQueueForeign = 228,
UnableToProcessInStream = 229,
UnableToProcessOutOfStream = 230,
TransactionTimeout = 231,
ActiveTimer = 232,
TupleFieldCountLimit = 233,
CreateConstraint = 234,
FieldConstraintFailed = 235,
TupleConstraintFailed = 236,
CreateForeignKey = 237,
ForeignKeyIntegrity = 238,
FieldForeignKeyFailed = 239,
ComplexForeignKeyFailed = 240,
WrongSpaceUpgradeOptions = 241,
NoElectionQuorum = 242,
Ssl = 243,
SplitBrain = 244,
OldTerm = 245,
InterferingElections = 246,
InvalidIteratorPosition = 247,
InvalidDefaultValueType = 248,
UnknownAuthMethod = 249,
InvalidAuthData = 250,
InvalidAuthRequest = 251,
WeakPassword = 252,
OldPassword = 253,
NoSuchSession = 254,
WrongSessionType = 255,
PasswordExpired = 256,
AuthDelay = 257,
AuthRequired = 258,
SqlSeqScan = 259,
NoSuchEvent = 260,
BootstrapNotUnanimous = 261,
CantCheckBootstrapLeader = 262,
BootstrapConnectionNotToAll = 263,
NilUuid = 264,
WrongFunctionOptions = 265,
MissingSystemSpaces = 266,
ExceededVdbeMaxSteps = 267,
IllegalOptions = 268,
IllegalOptionsFormat = 269,
CantGenerateUuid = 270,
SqlStatementBusy = 271,
SchemaUpdateInProgress = 272,
Unused7 = 273,
Unconfigured = 274,
}
}
#[allow(clippy::assertions_on_constants)]
const _: () = {
assert!(TarantoolErrorCode::DISCRIMINANTS_ARE_SUBSEQUENT);
};
impl TarantoolErrorCode {
pub fn try_last() -> Option<Self> {
unsafe {
let e_ptr = ffi::box_error_last();
if e_ptr.is_null() {
return None;
}
let u32_code = ffi::box_error_code(e_ptr);
TarantoolErrorCode::from_i64(u32_code as _)
}
}
pub fn last() -> Self {
Self::try_last().unwrap()
}
}
impl From<TarantoolErrorCode> for u32 {
#[inline(always)]
fn from(code: TarantoolErrorCode) -> u32 {
code as _
}
}
pub fn clear_error() {
unsafe { ffi::box_error_clear() }
}
#[macro_export]
macro_rules! set_error {
($code:expr, $($msg_args:tt)+) => {{
let msg = ::std::fmt::format(::std::format_args!($($msg_args)+));
let msg = ::std::ffi::CString::new(msg).unwrap();
$crate::error::set_last_error(None, $code as _, &msg);
}};
}
#[macro_export]
#[deprecated = "use `BoxError::new` instead"]
macro_rules! set_and_get_error {
($code:expr, $($msg_args:tt)+) => {{
$crate::set_error!($code, $($msg_args)+);
$crate::error::BoxError::last()
}};
}
#[deprecated = "use `EncodeError` instead"]
pub type Encode = EncodeError;
#[derive(Debug, thiserror::Error)]
pub enum EncodeError {
#[error("{0}")]
Rmp(#[from] rmp_serde::encode::Error),
#[error("invalid msgpack value (expected array, found {:?})", crate::util::DebugAsMPValue(.0))]
InvalidMP(Vec<u8>),
}
#[test]
fn tarantool_error_doesnt_depend_on_link_error() {
let err = Error::from(rmp_serde::decode::Error::OutOfRange);
assert!(!err.to_string().is_empty());
assert!(!format!("{err}").is_empty());
}
#[cfg(feature = "internal_test")]
mod tests {
use super::*;
#[crate::test(tarantool = "crate")]
fn set_error_expands_format() {
let msg = "my message";
set_error!(TarantoolErrorCode::Unknown, "{msg}");
let e = BoxError::last();
assert_eq!(e.to_string(), "Unknown: my message");
}
#[crate::test(tarantool = "crate")]
fn set_error_format_sequences() {
for c in b'a'..=b'z' {
let c = c as char;
set_error!(TarantoolErrorCode::Unknown, "%{c}");
let e = BoxError::last();
assert_eq!(e.to_string(), format!("Unknown: %{c}"));
}
}
#[crate::test(tarantool = "crate")]
fn set_error_caller_location() {
fn no_track_caller() {
set_error!(TarantoolErrorCode::Unknown, "custom error");
}
let line_1 = line!() - 2;
no_track_caller();
let e = BoxError::last();
assert_eq!(e.file(), Some(file!()));
assert_eq!(e.line(), Some(line_1));
#[track_caller]
fn with_track_caller() {
set_error!(TarantoolErrorCode::Unknown, "custom error");
}
with_track_caller();
let line_2 = line!() - 1;
let e = BoxError::last();
assert_eq!(e.file(), Some(file!()));
assert_eq!(e.line(), Some(line_2));
set_last_error(
Some(("foobar", 420)),
69,
crate::c_str!("super custom error"),
);
let e = BoxError::last();
assert_eq!(e.file(), Some("foobar"));
assert_eq!(e.line(), Some(420));
}
#[crate::test(tarantool = "crate")]
fn box_error_location() {
fn no_track_caller() {
let e = BoxError::new(69105_u32, "too many leaves");
e.set_last();
}
let line_1 = line!() - 3;
no_track_caller();
let e = BoxError::last();
assert_eq!(e.file(), Some(file!()));
assert_eq!(e.line(), Some(line_1));
#[track_caller]
fn with_track_caller() {
let e = BoxError::new(69105_u32, "too many leaves");
e.set_last();
}
with_track_caller();
let line_2 = line!() - 1;
let e = BoxError::last();
assert_eq!(e.file(), Some(file!()));
assert_eq!(e.line(), Some(line_2));
BoxError::with_location(69105_u32, "too many leaves", "nice", 69).set_last();
let e = BoxError::last();
assert_eq!(e.file(), Some("nice"));
assert_eq!(e.line(), Some(69));
}
#[crate::test(tarantool = "crate")]
#[allow(clippy::let_unit_value)]
fn set_error_with_no_semicolon() {
() = set_error!(TarantoolErrorCode::Unknown, "idk");
if true {
set_error!(TarantoolErrorCode::Unknown, "idk")
} else {
unreachable!()
}; }
#[crate::test(tarantool = "crate")]
fn tarantool_error_use_after_free() {
set_error!(TarantoolErrorCode::Unknown, "foo");
let e = BoxError::last();
assert_eq!(e.error_type(), "ClientError");
clear_error();
assert_eq!(e.error_type(), "ClientError");
}
#[crate::test(tarantool = "crate")]
fn tarantool_error_fields_round_trip() {
let mut error = BoxError::new(12345_u32, "complex error");
error.set_field("igneous", "mystery".into());
error.set_last();
let error = BoxError::maybe_last().unwrap_err();
assert_eq!(error.error_code(), 12345_u32);
assert_eq!(error.message(), "complex error");
if cfg!(feature = "picodata") {
assert_eq!(error.field("igneous"), Some(&"mystery".into()));
} else {
assert!(error.fields().is_empty());
}
}
}