fory_core/error.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! # PERFORMANCE CRITICAL MODULE
19//!
20//! **WARNING**: This module is highly performance-sensitive. Changes to error
21//! constructor attributes (`#[inline]`, `#[cold]`, `#[track_caller]`) can
22//! impact serialization/deserialization performance throughout the entire codebase.
23//!
24//! ## Why This Module Is Performance Critical
25//!
26//! Error constructors are called in **every** buffer read/write operation and type check.
27//! Even though these functions are rarely executed (error paths), their mere presence and
28//! inlining behavior affects how LLVM optimizes the **hot paths** (successful operations).
29
30use std::borrow::Cow;
31
32use crate::types::format_type_id;
33use thiserror::Error;
34
35/// Global flag to check if FORY_PANIC_ON_ERROR environment variable is set at compile time.
36/// Set FORY_PANIC_ON_ERROR=1 (or true) at compile time to enable panic on error.
37const fn is_truthy_env(value: &str) -> bool {
38 let bytes = value.as_bytes();
39 (bytes.len() == 1 && bytes[0] == b'1')
40 || (bytes.len() == 4
41 && (bytes[0] | 0x20) == b't'
42 && (bytes[1] | 0x20) == b'r'
43 && (bytes[2] | 0x20) == b'u'
44 && (bytes[3] | 0x20) == b'e')
45}
46
47pub const PANIC_ON_ERROR: bool = match option_env!("FORY_PANIC_ON_ERROR") {
48 Some(value) => is_truthy_env(value),
49 None => false,
50};
51
52/// Check if FORY_PANIC_ON_ERROR environment variable is set.
53#[inline(always)]
54pub const fn should_panic_on_error() -> bool {
55 PANIC_ON_ERROR
56}
57
58/// Error type for Fory serialization and deserialization operations.
59///
60/// # IMPORTANT: Always Use Static Constructor Functions
61///
62/// **DO NOT** construct error variants directly using the enum syntax.
63/// **ALWAYS** use the provided static constructor functions instead.
64///
65/// ## Why Use Static Functions?
66///
67/// The static constructor functions provide:
68/// - Automatic type conversion via `Into<Cow<'static, str>>`
69/// - Consistent error creation across the codebase
70/// - Better ergonomics (no need for manual `.into()` calls)
71/// - Future-proof API if error construction logic needs to change
72///
73/// ## Examples
74///
75/// ```rust
76/// use fory_core::error::Error;
77///
78/// // ✅ CORRECT: Use static functions
79/// let err = Error::type_error("Expected string type");
80/// let err = Error::invalid_data(format!("Invalid value: {}", 42));
81/// let err = Error::type_mismatch(1, 2);
82///
83/// // ❌ WRONG: Do not construct directly
84/// // let err = Error::TypeError("Expected string type".into());
85/// // let err = Error::InvalidData(format!("Invalid value: {}", 42).into());
86/// ```
87///
88/// ## Available Constructor Functions
89///
90/// - [`Error::type_mismatch`] - For type ID mismatches
91/// - [`Error::buffer_out_of_bound`] - For buffer boundary violations
92/// - [`Error::encode_error`] - For encoding failures
93/// - [`Error::invalid_data`] - For invalid or corrupted data
94/// - [`Error::invalid_ref`] - For invalid reference IDs
95/// - [`Error::unknown_enum`] - For unknown enum variants
96/// - [`Error::type_error`] - For general type errors
97/// - [`Error::encoding_error`] - For encoding format errors
98/// - [`Error::depth_exceed`] - For exceeding maximum nesting depth
99/// - [`Error::unsupported`] - For unsupported operations
100/// - [`Error::not_allowed`] - For disallowed operations
101/// - [`Error::unknown`] - For generic errors
102///
103/// ## Debug Mode: FORY_PANIC_ON_ERROR
104///
105/// For easier debugging, you can set the `FORY_PANIC_ON_ERROR` environment variable to make
106/// the program panic at the exact location where an error is created. This helps identify
107/// the error source with a full stack trace.
108///
109/// ```bash
110/// RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 cargo run
111/// # or
112/// RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=true cargo test
113/// ```
114///
115/// When enabled, any error created via the static constructor functions will panic immediately
116/// with the error message, allowing you to see the exact call stack in your debugger or
117/// panic output. Use `RUST_BACKTRACE=1` together with `FORY_PANIC_ON_ERROR` to get a full
118/// stack trace showing exactly where the error was created.
119#[derive(Error, Debug)]
120#[non_exhaustive]
121pub enum Error {
122 /// Type mismatch between local and remote type IDs.
123 ///
124 /// Do not construct this variant directly; use [`Error::type_mismatch`] instead.
125 #[error("{0}")]
126 TypeMismatch(Cow<'static, str>),
127
128 /// Buffer boundary violation during read/write operations.
129 ///
130 /// Do not construct this variant directly; use [`Error::buffer_out_of_bound`] instead.
131 #[error("Buffer out of bound: {0} + {1} > {2}")]
132 BufferOutOfBound(usize, usize, usize),
133
134 /// Error during data encoding.
135 ///
136 /// Do not construct this variant directly; use [`Error::encode_error`] instead.
137 #[error("{0}")]
138 EncodeError(Cow<'static, str>),
139
140 /// Invalid or corrupted data encountered.
141 ///
142 /// Do not construct this variant directly; use [`Error::invalid_data`] instead.
143 #[error("{0}")]
144 InvalidData(Cow<'static, str>),
145
146 /// Invalid reference ID encountered.
147 ///
148 /// Do not construct this variant directly; use [`Error::invalid_ref`] instead.
149 #[error("{0}")]
150 InvalidRef(Cow<'static, str>),
151
152 /// Unknown enum variant encountered.
153 ///
154 /// Do not construct this variant directly; use [`Error::unknown_enum`] instead.
155 #[error("{0}")]
156 UnknownEnum(Cow<'static, str>),
157
158 /// General type-related error.
159 ///
160 /// Do not construct this variant directly; use [`Error::type_error`] instead.
161 #[error("{0}")]
162 TypeError(Cow<'static, str>),
163
164 /// Error in encoding format or conversion.
165 ///
166 /// Do not construct this variant directly; use [`Error::encoding_error`] instead.
167 #[error("{0}")]
168 EncodingError(Cow<'static, str>),
169
170 /// Maximum nesting depth exceeded.
171 ///
172 /// Do not construct this variant directly; use [`Error::depth_exceed`] instead.
173 #[error("{0}")]
174 DepthExceed(Cow<'static, str>),
175
176 /// Unsupported operation or feature.
177 ///
178 /// Do not construct this variant directly; use [`Error::unsupported`] instead.
179 #[error("{0}")]
180 Unsupported(Cow<'static, str>),
181
182 /// Operation not allowed in current context.
183 ///
184 /// Do not construct this variant directly; use [`Error::not_allowed`] instead.
185 #[error("{0}")]
186 NotAllowed(Cow<'static, str>),
187
188 /// Generic unknown error.
189 ///
190 /// Do not construct this variant directly; use [`Error::unknown`] instead.
191 #[error("{0}")]
192 Unknown(Cow<'static, str>),
193
194 /// Struct version mismatch between local and remote schemas.
195 ///
196 /// Do not construct this variant directly; use [`Error::struct_version_mismatch`] instead.
197 #[error("{0}")]
198 StructVersionMismatch(Cow<'static, str>),
199}
200
201impl Error {
202 /// Creates a new [`Error::TypeMismatch`] with the given type IDs.
203 ///
204 /// The error message will display both the original registered ID and the internal type name
205 /// for user-registered types, making debugging easier.
206 ///
207 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
208 ///
209 /// # Example
210 /// ```
211 /// use fory_core::error::Error;
212 ///
213 /// let err = Error::type_mismatch(1, 2);
214 /// ```
215 #[inline(always)]
216 #[cold]
217 #[track_caller]
218 pub fn type_mismatch(type_a: u32, type_b: u32) -> Self {
219 let msg = format!(
220 "Type mismatch: local {} vs remote {}",
221 format_type_id(type_a),
222 format_type_id(type_b)
223 );
224 let err = Error::TypeMismatch(Cow::Owned(msg));
225 if PANIC_ON_ERROR {
226 panic!("FORY_PANIC_ON_ERROR: {}", err);
227 }
228 err
229 }
230
231 /// Creates a new [`Error::BufferOutOfBound`] with the given bounds.
232 ///
233 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
234 ///
235 /// # Example
236 /// ```
237 /// use fory_core::error::Error;
238 ///
239 /// let err = Error::buffer_out_of_bound(10, 20, 25);
240 /// ```
241 #[inline(always)]
242 #[cold]
243 #[track_caller]
244 pub fn buffer_out_of_bound(offset: usize, length: usize, capacity: usize) -> Self {
245 let err = Error::BufferOutOfBound(offset, length, capacity);
246 if PANIC_ON_ERROR {
247 panic!("FORY_PANIC_ON_ERROR: {}", err);
248 }
249 err
250 }
251
252 /// Creates a new [`Error::EncodeError`] from a string or static message.
253 ///
254 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
255 ///
256 /// # Example
257 /// ```
258 /// use fory_core::error::Error;
259 ///
260 /// let err = Error::encode_error("Failed to encode");
261 /// let err = Error::encode_error(format!("Failed to encode field {}", "name"));
262 /// ```
263 #[inline(always)]
264 #[cold]
265 #[track_caller]
266 pub fn encode_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
267 let err = Error::EncodeError(s.into());
268 if PANIC_ON_ERROR {
269 panic!("FORY_PANIC_ON_ERROR: {}", err);
270 }
271 err
272 }
273
274 /// Creates a new [`Error::InvalidData`] from a string or static message.
275 ///
276 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
277 ///
278 /// # Example
279 /// ```
280 /// use fory_core::error::Error;
281 ///
282 /// let err = Error::invalid_data("Invalid data format");
283 /// let err = Error::invalid_data(format!("Invalid data at position {}", 42));
284 /// ```
285 #[inline(always)]
286 #[cold]
287 #[track_caller]
288 pub fn invalid_data<S: Into<Cow<'static, str>>>(s: S) -> Self {
289 let err = Error::InvalidData(s.into());
290 if PANIC_ON_ERROR {
291 panic!("FORY_PANIC_ON_ERROR: {}", err);
292 }
293 err
294 }
295
296 /// Creates a new [`Error::InvalidRef`] from a string or static message.
297 ///
298 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
299 ///
300 /// # Example
301 /// ```
302 /// use fory_core::error::Error;
303 ///
304 /// let err = Error::invalid_ref("Invalid reference");
305 /// let err = Error::invalid_ref(format!("Invalid ref id {}", 123));
306 /// ```
307 #[inline(always)]
308 #[cold]
309 #[track_caller]
310 pub fn invalid_ref<S: Into<Cow<'static, str>>>(s: S) -> Self {
311 let err = Error::InvalidRef(s.into());
312 if PANIC_ON_ERROR {
313 panic!("FORY_PANIC_ON_ERROR: {}", err);
314 }
315 err
316 }
317
318 /// Creates a new [`Error::UnknownEnum`] from a string or static message.
319 ///
320 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
321 ///
322 /// # Example
323 /// ```
324 /// use fory_core::error::Error;
325 ///
326 /// let err = Error::unknown_enum("Unknown enum variant");
327 /// let err = Error::unknown_enum(format!("Unknown variant {}", 5));
328 /// ```
329 #[inline(always)]
330 #[cold]
331 #[track_caller]
332 pub fn unknown_enum<S: Into<Cow<'static, str>>>(s: S) -> Self {
333 let err = Error::UnknownEnum(s.into());
334 if PANIC_ON_ERROR {
335 panic!("FORY_PANIC_ON_ERROR: {}", err);
336 }
337 err
338 }
339
340 /// Creates a new [`Error::TypeError`] from a string or static message.
341 ///
342 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
343 ///
344 /// # Example
345 /// ```
346 /// use fory_core::error::Error;
347 ///
348 /// let err = Error::type_error("Type error");
349 /// let err = Error::type_error(format!("Expected type {}", "String"));
350 /// ```
351 #[inline(always)]
352 #[cold]
353 #[track_caller]
354 pub fn type_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
355 let err = Error::TypeError(s.into());
356 if PANIC_ON_ERROR {
357 panic!("FORY_PANIC_ON_ERROR: {}", err);
358 }
359 err
360 }
361
362 /// Creates a new [`Error::EncodingError`] from a string or static message.
363 ///
364 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
365 ///
366 /// # Example
367 /// ```
368 /// use fory_core::error::Error;
369 ///
370 /// let err = Error::encoding_error("Encoding failed");
371 /// let err = Error::encoding_error(format!("Failed to encode as {}", "UTF-8"));
372 /// ```
373 #[inline(always)]
374 #[cold]
375 #[track_caller]
376 pub fn encoding_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
377 let err = Error::EncodingError(s.into());
378 if PANIC_ON_ERROR {
379 panic!("FORY_PANIC_ON_ERROR: {}", err);
380 }
381 err
382 }
383
384 /// Creates a new [`Error::DepthExceed`] from a string or static message.
385 ///
386 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
387 ///
388 /// # Example
389 /// ```
390 /// use fory_core::error::Error;
391 ///
392 /// let err = Error::depth_exceed("Max depth exceeded");
393 /// let err = Error::depth_exceed(format!("Depth {} exceeds max {}", 100, 64));
394 /// ```
395 #[inline(always)]
396 #[cold]
397 #[track_caller]
398 pub fn depth_exceed<S: Into<Cow<'static, str>>>(s: S) -> Self {
399 let err = Error::DepthExceed(s.into());
400 if PANIC_ON_ERROR {
401 panic!("FORY_PANIC_ON_ERROR: {}", err);
402 }
403 err
404 }
405
406 /// Creates a new [`Error::Unsupported`] from a string or static message.
407 ///
408 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
409 ///
410 /// # Example
411 /// ```
412 /// use fory_core::error::Error;
413 ///
414 /// let err = Error::unsupported("Unsupported operation");
415 /// let err = Error::unsupported(format!("Type {} not supported", "MyType"));
416 /// ```
417 #[inline(always)]
418 #[cold]
419 #[track_caller]
420 pub fn unsupported<S: Into<Cow<'static, str>>>(s: S) -> Self {
421 let err = Error::Unsupported(s.into());
422 if PANIC_ON_ERROR {
423 panic!("FORY_PANIC_ON_ERROR: {}", err);
424 }
425 err
426 }
427
428 /// Creates a new [`Error::NotAllowed`] from a string or static message.
429 ///
430 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
431 ///
432 /// # Example
433 /// ```
434 /// use fory_core::error::Error;
435 ///
436 /// let err = Error::not_allowed("Operation not allowed");
437 /// let err = Error::not_allowed(format!("Cannot perform {}", "delete"));
438 /// ```
439 #[inline(always)]
440 #[cold]
441 #[track_caller]
442 pub fn not_allowed<S: Into<Cow<'static, str>>>(s: S) -> Self {
443 let err = Error::NotAllowed(s.into());
444 if PANIC_ON_ERROR {
445 panic!("FORY_PANIC_ON_ERROR: {}", err);
446 }
447 err
448 }
449
450 /// Creates a new [`Error::StructVersionMismatch`] from a string or static message.
451 ///
452 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
453 ///
454 /// # Example
455 /// ```
456 /// use fory_core::error::Error;
457 ///
458 /// let err = Error::struct_version_mismatch("Version mismatch");
459 /// let err = Error::struct_version_mismatch(format!("Class {} version mismatch", "Foo"));
460 /// ```
461 #[inline(always)]
462 #[cold]
463 #[track_caller]
464 pub fn struct_version_mismatch<S: Into<Cow<'static, str>>>(s: S) -> Self {
465 let err = Error::StructVersionMismatch(s.into());
466 if PANIC_ON_ERROR {
467 panic!("FORY_PANIC_ON_ERROR: {}", err);
468 }
469 err
470 }
471
472 /// Creates a new [`Error::Unknown`] from a string or static message.
473 ///
474 /// This function is a convenient way to produce an error message
475 /// from a literal, `String`, or any type that can be converted into
476 /// a [`Cow<'static, str>`].
477 ///
478 /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
479 ///
480 /// # Example
481 /// ```
482 /// use fory_core::error::Error;
483 ///
484 /// let err = Error::unknown("Something went wrong");
485 /// let err = Error::unknown(format!("ID:{} not found", 1));
486 /// ```
487 #[inline(always)]
488 #[cold]
489 #[track_caller]
490 pub fn unknown<S: Into<Cow<'static, str>>>(s: S) -> Self {
491 let err = Error::Unknown(s.into());
492 if PANIC_ON_ERROR {
493 panic!("FORY_PANIC_ON_ERROR: {}", err);
494 }
495 err
496 }
497
498 /// Enhances a [`Error::TypeError`] with additional type name information.
499 ///
500 /// If the error is a `TypeError`, appends the type name to the message.
501 /// Otherwise, returns the error unchanged.
502 ///
503 /// # Example
504 /// ```
505 /// use fory_core::error::Error;
506 ///
507 /// let err = Error::type_error("Type not registered");
508 /// let enhanced = Error::enhance_type_error::<String>(err);
509 /// // Result: "Type not registered (type: alloc::string::String)"
510 /// ```
511 #[inline(never)]
512 pub fn enhance_type_error<T: ?Sized + 'static>(err: Error) -> Error {
513 if let Error::TypeError(s) = err {
514 let mut msg = s.to_string();
515 msg.push_str(" (type: ");
516 msg.push_str(std::any::type_name::<T>());
517 msg.push(')');
518 Error::type_error(msg)
519 } else {
520 err
521 }
522 }
523}
524
525/// Ensures a condition is true; otherwise returns an [`enum@Error`].
526///
527/// # Examples
528/// ```
529/// use fory_core::ensure;
530/// use fory_core::error::Error;
531///
532/// fn check_value(n: i32) -> Result<(), Error> {
533/// ensure!(n > 0, "value must be positive");
534/// ensure!(n < 10, "value {} too large", n);
535/// Ok(())
536/// }
537/// ```
538#[macro_export]
539macro_rules! ensure {
540 ($cond:expr, $msg:literal) => {
541 if !$cond {
542 return Err($crate::error::Error::unknown($msg));
543 }
544 };
545 ($cond:expr, $err:expr) => {
546 if !$cond {
547 return Err($err);
548 }
549 };
550 ($cond:expr, $fmt:expr, $($arg:tt)*) => {
551 if !$cond {
552 return Err($crate::error::Error::unknown(format!($fmt, $($arg)*)));
553 }
554 };
555}
556
557/// Returns early with an [`enum@Error`].
558///
559/// # Examples
560/// ```
561/// use fory_core::bail;
562/// use fory_core::error::Error;
563///
564/// fn fail_fast() -> Result<(), Error> {
565/// bail!("something went wrong");
566/// }
567/// ```
568#[macro_export]
569macro_rules! bail {
570 ($err:expr) => {
571 return Err($crate::error::Error::unknown($err))
572 };
573 ($fmt:expr, $($arg:tt)*) => {
574 return Err($crate::error::Error::unknown(format!($fmt, $($arg)*)))
575 };
576}
577
578/// Returns early with a [`Error::NotAllowed`].
579///
580/// # Examples
581/// ```
582/// use fory_core::not_allowed;
583/// use fory_core::error::Error;
584///
585/// fn check_operation() -> Result<(), Error> {
586/// not_allowed!("operation not allowed");
587/// }
588///
589/// fn check_operation_with_context(op: &str) -> Result<(), Error> {
590/// not_allowed!("operation {} not allowed", op);
591/// }
592/// ```
593#[macro_export]
594macro_rules! not_allowed {
595 ($err:expr) => {
596 return Err($crate::error::Error::not_allowed($err))
597 };
598 ($fmt:expr, $($arg:tt)*) => {
599 return Err($crate::error::Error::not_allowed(format!($fmt, $($arg)*)))
600 };
601}