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::type_id::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    /// Deserialization size limit exceeded.
201    ///
202    /// Returned when a payload-driven length exceeds a configured guardrail
203    /// (e.g. `max_binary_size` or `max_collection_size`).
204    ///
205    /// Do not construct this variant directly; use [`Error::size_limit_exceeded`] instead.
206    #[error("{0}")]
207    SizeLimitExceeded(Cow<'static, str>),
208}
209
210impl Error {
211    /// Creates a new [`Error::TypeMismatch`] with the given type IDs.
212    ///
213    /// The error message will display both the original registered ID and the internal type name
214    /// for user-registered types, making debugging easier.
215    ///
216    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
217    ///
218    /// # Example
219    /// ```
220    /// use fory_core::error::Error;
221    ///
222    /// let err = Error::type_mismatch(1, 2);
223    /// ```
224    #[inline(always)]
225    #[cold]
226    #[track_caller]
227    pub fn type_mismatch(type_a: u32, type_b: u32) -> Self {
228        let msg = format!(
229            "Type mismatch: local {} vs remote {}",
230            format_type_id(type_a),
231            format_type_id(type_b)
232        );
233        let err = Error::TypeMismatch(Cow::Owned(msg));
234        if PANIC_ON_ERROR {
235            panic!("FORY_PANIC_ON_ERROR: {}", err);
236        }
237        err
238    }
239
240    /// Creates a new [`Error::BufferOutOfBound`] with the given bounds.
241    ///
242    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
243    ///
244    /// # Example
245    /// ```
246    /// use fory_core::error::Error;
247    ///
248    /// let err = Error::buffer_out_of_bound(10, 20, 25);
249    /// ```
250    #[inline(always)]
251    #[cold]
252    #[track_caller]
253    pub fn buffer_out_of_bound(offset: usize, length: usize, capacity: usize) -> Self {
254        let err = Error::BufferOutOfBound(offset, length, capacity);
255        if PANIC_ON_ERROR {
256            panic!("FORY_PANIC_ON_ERROR: {}", err);
257        }
258        err
259    }
260
261    /// Creates a new [`Error::EncodeError`] from a string or static message.
262    ///
263    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
264    ///
265    /// # Example
266    /// ```
267    /// use fory_core::error::Error;
268    ///
269    /// let err = Error::encode_error("Failed to encode");
270    /// let err = Error::encode_error(format!("Failed to encode field {}", "name"));
271    /// ```
272    #[inline(always)]
273    #[cold]
274    #[track_caller]
275    pub fn encode_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
276        let err = Error::EncodeError(s.into());
277        if PANIC_ON_ERROR {
278            panic!("FORY_PANIC_ON_ERROR: {}", err);
279        }
280        err
281    }
282
283    /// Creates a new [`Error::InvalidData`] from a string or static message.
284    ///
285    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
286    ///
287    /// # Example
288    /// ```
289    /// use fory_core::error::Error;
290    ///
291    /// let err = Error::invalid_data("Invalid data format");
292    /// let err = Error::invalid_data(format!("Invalid data at position {}", 42));
293    /// ```
294    #[inline(always)]
295    #[cold]
296    #[track_caller]
297    pub fn invalid_data<S: Into<Cow<'static, str>>>(s: S) -> Self {
298        let err = Error::InvalidData(s.into());
299        if PANIC_ON_ERROR {
300            panic!("FORY_PANIC_ON_ERROR: {}", err);
301        }
302        err
303    }
304
305    /// Creates a new [`Error::InvalidRef`] from a string or static message.
306    ///
307    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
308    ///
309    /// # Example
310    /// ```
311    /// use fory_core::error::Error;
312    ///
313    /// let err = Error::invalid_ref("Invalid reference");
314    /// let err = Error::invalid_ref(format!("Invalid ref id {}", 123));
315    /// ```
316    #[inline(always)]
317    #[cold]
318    #[track_caller]
319    pub fn invalid_ref<S: Into<Cow<'static, str>>>(s: S) -> Self {
320        let err = Error::InvalidRef(s.into());
321        if PANIC_ON_ERROR {
322            panic!("FORY_PANIC_ON_ERROR: {}", err);
323        }
324        err
325    }
326
327    /// Creates a new [`Error::UnknownEnum`] from a string or static message.
328    ///
329    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
330    ///
331    /// # Example
332    /// ```
333    /// use fory_core::error::Error;
334    ///
335    /// let err = Error::unknown_enum("Unknown enum variant");
336    /// let err = Error::unknown_enum(format!("Unknown variant {}", 5));
337    /// ```
338    #[inline(always)]
339    #[cold]
340    #[track_caller]
341    pub fn unknown_enum<S: Into<Cow<'static, str>>>(s: S) -> Self {
342        let err = Error::UnknownEnum(s.into());
343        if PANIC_ON_ERROR {
344            panic!("FORY_PANIC_ON_ERROR: {}", err);
345        }
346        err
347    }
348
349    /// Creates a new [`Error::TypeError`] from a string or static message.
350    ///
351    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
352    ///
353    /// # Example
354    /// ```
355    /// use fory_core::error::Error;
356    ///
357    /// let err = Error::type_error("Type error");
358    /// let err = Error::type_error(format!("Expected type {}", "String"));
359    /// ```
360    #[inline(always)]
361    #[cold]
362    #[track_caller]
363    pub fn type_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
364        let err = Error::TypeError(s.into());
365        if PANIC_ON_ERROR {
366            panic!("FORY_PANIC_ON_ERROR: {}", err);
367        }
368        err
369    }
370
371    /// Creates a new [`Error::EncodingError`] from a string or static message.
372    ///
373    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
374    ///
375    /// # Example
376    /// ```
377    /// use fory_core::error::Error;
378    ///
379    /// let err = Error::encoding_error("Encoding failed");
380    /// let err = Error::encoding_error(format!("Failed to encode as {}", "UTF-8"));
381    /// ```
382    #[inline(always)]
383    #[cold]
384    #[track_caller]
385    pub fn encoding_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
386        let err = Error::EncodingError(s.into());
387        if PANIC_ON_ERROR {
388            panic!("FORY_PANIC_ON_ERROR: {}", err);
389        }
390        err
391    }
392
393    /// Creates a new [`Error::DepthExceed`] from a string or static message.
394    ///
395    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
396    ///
397    /// # Example
398    /// ```
399    /// use fory_core::error::Error;
400    ///
401    /// let err = Error::depth_exceed("Max depth exceeded");
402    /// let err = Error::depth_exceed(format!("Depth {} exceeds max {}", 100, 64));
403    /// ```
404    #[inline(always)]
405    #[cold]
406    #[track_caller]
407    pub fn depth_exceed<S: Into<Cow<'static, str>>>(s: S) -> Self {
408        let err = Error::DepthExceed(s.into());
409        if PANIC_ON_ERROR {
410            panic!("FORY_PANIC_ON_ERROR: {}", err);
411        }
412        err
413    }
414
415    /// Creates a new [`Error::Unsupported`] from a string or static message.
416    ///
417    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
418    ///
419    /// # Example
420    /// ```
421    /// use fory_core::error::Error;
422    ///
423    /// let err = Error::unsupported("Unsupported operation");
424    /// let err = Error::unsupported(format!("Type {} not supported", "MyType"));
425    /// ```
426    #[inline(always)]
427    #[cold]
428    #[track_caller]
429    pub fn unsupported<S: Into<Cow<'static, str>>>(s: S) -> Self {
430        let err = Error::Unsupported(s.into());
431        if PANIC_ON_ERROR {
432            panic!("FORY_PANIC_ON_ERROR: {}", err);
433        }
434        err
435    }
436
437    /// Creates a new [`Error::NotAllowed`] from a string or static message.
438    ///
439    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
440    ///
441    /// # Example
442    /// ```
443    /// use fory_core::error::Error;
444    ///
445    /// let err = Error::not_allowed("Operation not allowed");
446    /// let err = Error::not_allowed(format!("Cannot perform {}", "delete"));
447    /// ```
448    #[inline(always)]
449    #[cold]
450    #[track_caller]
451    pub fn not_allowed<S: Into<Cow<'static, str>>>(s: S) -> Self {
452        let err = Error::NotAllowed(s.into());
453        if PANIC_ON_ERROR {
454            panic!("FORY_PANIC_ON_ERROR: {}", err);
455        }
456        err
457    }
458
459    /// Creates a new [`Error::StructVersionMismatch`] from a string or static message.
460    ///
461    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
462    ///
463    /// # Example
464    /// ```
465    /// use fory_core::error::Error;
466    ///
467    /// let err = Error::struct_version_mismatch("Version mismatch");
468    /// let err = Error::struct_version_mismatch(format!("Class {} version mismatch", "Foo"));
469    /// ```
470    #[inline(always)]
471    #[cold]
472    #[track_caller]
473    pub fn struct_version_mismatch<S: Into<Cow<'static, str>>>(s: S) -> Self {
474        let err = Error::StructVersionMismatch(s.into());
475        if PANIC_ON_ERROR {
476            panic!("FORY_PANIC_ON_ERROR: {}", err);
477        }
478        err
479    }
480
481    /// Creates a new [`Error::Unknown`] from a string or static message.
482    ///
483    /// This function is a convenient way to produce an error message
484    /// from a literal, `String`, or any type that can be converted into
485    /// a [`Cow<'static, str>`].
486    ///
487    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
488    ///
489    /// # Example
490    /// ```
491    /// use fory_core::error::Error;
492    ///
493    /// let err = Error::unknown("Something went wrong");
494    /// let err = Error::unknown(format!("ID:{} not found", 1));
495    /// ```
496    #[inline(always)]
497    #[cold]
498    #[track_caller]
499    pub fn unknown<S: Into<Cow<'static, str>>>(s: S) -> Self {
500        let err = Error::Unknown(s.into());
501        if PANIC_ON_ERROR {
502            panic!("FORY_PANIC_ON_ERROR: {}", err);
503        }
504        err
505    }
506
507    /// Creates a new [`Error::SizeLimitExceeded`] from a string or static message.
508    ///
509    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
510    ///
511    /// # Example
512    /// ```
513    /// use fory_core::error::Error;
514    ///
515    /// let err = Error::size_limit_exceeded("Collection size 2000000 exceeds limit 1048576");
516    /// ```
517    #[inline(always)]
518    #[cold]
519    #[track_caller]
520    pub fn size_limit_exceeded<S: Into<Cow<'static, str>>>(s: S) -> Self {
521        let err = Error::SizeLimitExceeded(s.into());
522        if PANIC_ON_ERROR {
523            panic!("FORY_PANIC_ON_ERROR: {}", err);
524        }
525        err
526    }
527
528    /// Enhances a [`Error::TypeError`] with additional type name information.
529    ///
530    /// If the error is a `TypeError`, appends the type name to the message.
531    /// Otherwise, returns the error unchanged.
532    ///
533    /// # Example
534    /// ```
535    /// use fory_core::error::Error;
536    ///
537    /// let err = Error::type_error("Type not registered");
538    /// let enhanced = Error::enhance_type_error::<String>(err);
539    /// // Result: "Type not registered (type: alloc::string::String)"
540    /// ```
541    #[inline(never)]
542    pub fn enhance_type_error<T: ?Sized + 'static>(err: Error) -> Error {
543        if let Error::TypeError(s) = err {
544            let mut msg = s.to_string();
545            msg.push_str(" (type: ");
546            msg.push_str(std::any::type_name::<T>());
547            msg.push(')');
548            Error::type_error(msg)
549        } else {
550            err
551        }
552    }
553}
554
555/// Ensures a condition is true; otherwise returns an [`enum@Error`].
556///
557/// # Examples
558/// ```
559/// use fory_core::ensure;
560/// use fory_core::error::Error;
561///
562/// fn check_value(n: i32) -> Result<(), Error> {
563///     ensure!(n > 0, "value must be positive");
564///     ensure!(n < 10, "value {} too large", n);
565///     Ok(())
566/// }
567/// ```
568#[macro_export]
569macro_rules! ensure {
570    ($cond:expr, $msg:literal) => {
571        if !$cond {
572            return Err($crate::error::Error::unknown($msg));
573        }
574    };
575    ($cond:expr, $err:expr) => {
576        if !$cond {
577            return Err($err);
578        }
579    };
580    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
581        if !$cond {
582            return Err($crate::error::Error::unknown(format!($fmt, $($arg)*)));
583        }
584    };
585}
586
587/// Returns early with an [`enum@Error`].
588///
589/// # Examples
590/// ```
591/// use fory_core::bail;
592/// use fory_core::error::Error;
593///
594/// fn fail_fast() -> Result<(), Error> {
595///     bail!("something went wrong");
596/// }
597/// ```
598#[macro_export]
599macro_rules! bail {
600    ($err:expr) => {
601        return Err($crate::error::Error::unknown($err))
602    };
603    ($fmt:expr, $($arg:tt)*) => {
604        return Err($crate::error::Error::unknown(format!($fmt, $($arg)*)))
605    };
606}
607
608/// Returns early with a [`Error::NotAllowed`].
609///
610/// # Examples
611/// ```
612/// use fory_core::not_allowed;
613/// use fory_core::error::Error;
614///
615/// fn check_operation() -> Result<(), Error> {
616///     not_allowed!("operation not allowed");
617/// }
618///
619/// fn check_operation_with_context(op: &str) -> Result<(), Error> {
620///     not_allowed!("operation {} not allowed", op);
621/// }
622/// ```
623#[macro_export]
624macro_rules! not_allowed {
625    ($err:expr) => {
626        return Err($crate::error::Error::not_allowed($err))
627    };
628    ($fmt:expr, $($arg:tt)*) => {
629        return Err($crate::error::Error::not_allowed(format!($fmt, $($arg)*)))
630    };
631}