1use 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
35pub type Result<T, E = Error> = std::result::Result<T, E>;
37
38pub type TimeoutError<E> = crate::fiber::r#async::timeout::Error<E>;
39
40#[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("server responded with error: {0}")]
85 Remote(BoxError),
86
87 #[error("{0}")]
88 Protocol(#[from] crate::network::protocol::ProtocolError),
89
90 #[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 #[error("{0}")]
112 ConnectionClosed(Arc<Error>),
113
114 #[error("{0}")]
117 Other(Box<dyn std::error::Error + Send + Sync>),
118}
119
120const _: () = {
121 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 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 error.utf8_error().into()
228 }
229}
230
231#[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
252pub type TarantoolError = BoxError;
254
255impl BoxError {
256 #[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 #[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 #[inline]
297 pub fn maybe_last() -> std::result::Result<(), Self> {
298 let error_ptr = unsafe { ffi::box_error_last() };
300 let Some(error_ptr) = NonNull::new(error_ptr) else {
301 return Ok(());
302 };
303
304 Err(unsafe { Self::from_ptr(error_ptr) })
306 }
307
308 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 #[inline(always)]
346 pub fn last() -> Self {
347 Self::maybe_last().err().unwrap()
348 }
349
350 #[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 #[inline(always)]
366 pub fn error_code(&self) -> u32 {
367 self.code
368 }
369
370 #[inline(always)]
372 pub fn error_type(&self) -> &str {
373 self.error_type.as_deref().unwrap_or("Unknown")
374 }
375
376 #[inline(always)]
378 pub fn message(&self) -> &str {
379 self.message
380 .as_deref()
381 .unwrap_or("<error message is missing>")
382 }
383
384 #[inline(always)]
387 pub fn file(&self) -> Option<&str> {
388 self.file.as_deref()
389 }
390
391 #[inline(always)]
394 pub fn line(&self) -> Option<u32> {
395 self.line
396 }
397
398 #[inline(always)]
404 pub fn errno(&self) -> Option<u32> {
405 self.errno
406 }
407
408 #[inline(always)]
410 pub fn cause(&self) -> Option<&Self> {
411 self.cause.as_deref()
412 }
413
414 #[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
436unsafe 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 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 let line_ptr = ptr.add(line_ofs as _).cast::<u32>();
474 let line = *line_ptr;
475
476 Some((file, line))
477}
478
479#[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 let file = to_cstring_lossy(file);
493
494 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
507pub trait IntoBoxError: Sized + Display {
521 #[inline(always)]
523 #[track_caller]
524 fn set_last_error(self) {
525 self.into_box_error().set_last();
526 }
527
528 #[track_caller]
530 #[inline(always)]
531 fn into_box_error(self) -> BoxError {
532 BoxError::new(self.error_code(), self.to_string())
533 }
534
535 #[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 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
660crate::define_enum_with_introspection! {
665 #[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
976pub fn clear_error() {
982 unsafe { ffi::box_error_clear() }
983}
984
985#[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#[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#[deprecated = "use `EncodeError` instead"]
1030pub type Encode = EncodeError;
1031
1032#[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#[test]
1047fn tarantool_error_doesnt_depend_on_link_error() {
1048 let err = Error::from(rmp_serde::decode::Error::OutOfRange);
1049 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 fn no_track_caller() {
1084 set_error!(TarantoolErrorCode::Unknown, "custom error");
1085 }
1086 let line_1 = line!() - 2; 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 #[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; let e = BoxError::last();
1105 assert_eq!(e.file(), Some(file!()));
1106 assert_eq!(e.line(), Some(line_2));
1107
1108 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 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; 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 #[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; let e = BoxError::last();
1150 assert_eq!(e.file(), Some(file!()));
1151 assert_eq!(e.line(), Some(line_2));
1152
1153 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 () = set_error!(TarantoolErrorCode::Unknown, "idk");
1170
1171 if true {
1172 set_error!(TarantoolErrorCode::Unknown, "idk")
1173 } else {
1174 unreachable!()
1175 }; }
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 assert_eq!(e.error_type(), "ClientError");
1189 }
1190}