Skip to main content

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}