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
18use std::borrow::Cow;
19use std::sync::OnceLock;
20
21use thiserror::Error;
22
23/// Global flag to check if FORY_PANIC_ON_ERROR environment variable is set.
24static PANIC_ON_ERROR: OnceLock<bool> = OnceLock::new();
25
26/// Check if FORY_PANIC_ON_ERROR environment variable is set.
27#[inline]
28pub fn should_panic_on_error() -> bool {
29    *PANIC_ON_ERROR.get_or_init(|| {
30        std::env::var("FORY_PANIC_ON_ERROR")
31            .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
32            .unwrap_or(false)
33    })
34}
35
36/// Error type for Fory serialization and deserialization operations.
37///
38/// # IMPORTANT: Always Use Static Constructor Functions
39///
40/// **DO NOT** construct error variants directly using the enum syntax.
41/// **ALWAYS** use the provided static constructor functions instead.
42///
43/// ## Why Use Static Functions?
44///
45/// The static constructor functions provide:
46/// - Automatic type conversion via `Into<Cow<'static, str>>`
47/// - Consistent error creation across the codebase
48/// - Better ergonomics (no need for manual `.into()` calls)
49/// - Future-proof API if error construction logic needs to change
50///
51/// ## Examples
52///
53/// ```rust
54/// use fory_core::error::Error;
55///
56/// // ✅ CORRECT: Use static functions
57/// let err = Error::type_error("Expected string type");
58/// let err = Error::invalid_data(format!("Invalid value: {}", 42));
59/// let err = Error::type_mismatch(1, 2);
60///
61/// // ❌ WRONG: Do not construct directly
62/// // let err = Error::TypeError("Expected string type".into());
63/// // let err = Error::InvalidData(format!("Invalid value: {}", 42).into());
64/// ```
65///
66/// ## Available Constructor Functions
67///
68/// - [`Error::type_mismatch`] - For type ID mismatches
69/// - [`Error::buffer_out_of_bound`] - For buffer boundary violations
70/// - [`Error::encode_error`] - For encoding failures
71/// - [`Error::invalid_data`] - For invalid or corrupted data
72/// - [`Error::invalid_ref`] - For invalid reference IDs
73/// - [`Error::unknown_enum`] - For unknown enum variants
74/// - [`Error::type_error`] - For general type errors
75/// - [`Error::encoding_error`] - For encoding format errors
76/// - [`Error::depth_exceed`] - For exceeding maximum nesting depth
77/// - [`Error::unsupported`] - For unsupported operations
78/// - [`Error::not_allowed`] - For disallowed operations
79/// - [`Error::unknown`] - For generic errors
80///
81/// ## Debug Mode: FORY_PANIC_ON_ERROR
82///
83/// For easier debugging, you can set the `FORY_PANIC_ON_ERROR` environment variable to make
84/// the program panic at the exact location where an error is created. This helps identify
85/// the error source with a full stack trace.
86///
87/// ```bash
88/// RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 cargo run
89/// # or
90/// RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=true cargo test
91/// ```
92///
93/// When enabled, any error created via the static constructor functions will panic immediately
94/// with the error message, allowing you to see the exact call stack in your debugger or
95/// panic output. Use `RUST_BACKTRACE=1` together with `FORY_PANIC_ON_ERROR` to get a full
96/// stack trace showing exactly where the error was created.
97#[derive(Error, Debug)]
98#[non_exhaustive]
99pub enum Error {
100    /// Type mismatch between local and remote type IDs.
101    ///
102    /// Do not construct this variant directly; use [`Error::type_mismatch`] instead.
103    #[error("Type mismatch: type_a = {0}, type_b = {1}")]
104    TypeMismatch(u32, u32),
105
106    /// Buffer boundary violation during read/write operations.
107    ///
108    /// Do not construct this variant directly; use [`Error::buffer_out_of_bound`] instead.
109    #[error("Buffer out of bound: {0} + {1} > {2}")]
110    BufferOutOfBound(usize, usize, usize),
111
112    /// Error during data encoding.
113    ///
114    /// Do not construct this variant directly; use [`Error::encode_error`] instead.
115    #[error("{0}")]
116    EncodeError(Cow<'static, str>),
117
118    /// Invalid or corrupted data encountered.
119    ///
120    /// Do not construct this variant directly; use [`Error::invalid_data`] instead.
121    #[error("{0}")]
122    InvalidData(Cow<'static, str>),
123
124    /// Invalid reference ID encountered.
125    ///
126    /// Do not construct this variant directly; use [`Error::invalid_ref`] instead.
127    #[error("{0}")]
128    InvalidRef(Cow<'static, str>),
129
130    /// Unknown enum variant encountered.
131    ///
132    /// Do not construct this variant directly; use [`Error::unknown_enum`] instead.
133    #[error("{0}")]
134    UnknownEnum(Cow<'static, str>),
135
136    /// General type-related error.
137    ///
138    /// Do not construct this variant directly; use [`Error::type_error`] instead.
139    #[error("{0}")]
140    TypeError(Cow<'static, str>),
141
142    /// Error in encoding format or conversion.
143    ///
144    /// Do not construct this variant directly; use [`Error::encoding_error`] instead.
145    #[error("{0}")]
146    EncodingError(Cow<'static, str>),
147
148    /// Maximum nesting depth exceeded.
149    ///
150    /// Do not construct this variant directly; use [`Error::depth_exceed`] instead.
151    #[error("{0}")]
152    DepthExceed(Cow<'static, str>),
153
154    /// Unsupported operation or feature.
155    ///
156    /// Do not construct this variant directly; use [`Error::unsupported`] instead.
157    #[error("{0}")]
158    Uunsupported(Cow<'static, str>),
159
160    /// Operation not allowed in current context.
161    ///
162    /// Do not construct this variant directly; use [`Error::not_allowed`] instead.
163    #[error("{0}")]
164    NotAllowed(Cow<'static, str>),
165
166    /// Generic unknown error.
167    ///
168    /// Do not construct this variant directly; use [`Error::unknown`] instead.
169    #[error("{0}")]
170    Unknown(Cow<'static, str>),
171
172    /// Struct version mismatch between local and remote schemas.
173    ///
174    /// Do not construct this variant directly; use [`Error::struct_version_mismatch`] instead.
175    #[error("{0}")]
176    StructVersionMismatch(Cow<'static, str>),
177}
178
179impl Error {
180    /// Creates a new [`Error::TypeMismatch`] with the given type IDs.
181    ///
182    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
183    ///
184    /// # Example
185    /// ```
186    /// use fory_core::error::Error;
187    ///
188    /// let err = Error::type_mismatch(1, 2);
189    /// ```
190    #[inline(always)]
191    #[track_caller]
192    pub fn type_mismatch(type_a: u32, type_b: u32) -> Self {
193        let err = Error::TypeMismatch(type_a, type_b);
194        if should_panic_on_error() {
195            panic!("FORY_PANIC_ON_ERROR: {}", err);
196        }
197        err
198    }
199
200    /// Creates a new [`Error::BufferOutOfBound`] with the given bounds.
201    ///
202    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
203    ///
204    /// # Example
205    /// ```
206    /// use fory_core::error::Error;
207    ///
208    /// let err = Error::buffer_out_of_bound(10, 20, 25);
209    /// ```
210    #[inline(always)]
211    #[track_caller]
212    pub fn buffer_out_of_bound(offset: usize, length: usize, capacity: usize) -> Self {
213        let err = Error::BufferOutOfBound(offset, length, capacity);
214        if should_panic_on_error() {
215            panic!("FORY_PANIC_ON_ERROR: {}", err);
216        }
217        err
218    }
219
220    /// Creates a new [`Error::EncodeError`] from a string or static message.
221    ///
222    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
223    ///
224    /// # Example
225    /// ```
226    /// use fory_core::error::Error;
227    ///
228    /// let err = Error::encode_error("Failed to encode");
229    /// let err = Error::encode_error(format!("Failed to encode field {}", "name"));
230    /// ```
231    #[inline(always)]
232    #[track_caller]
233    pub fn encode_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
234        let err = Error::EncodeError(s.into());
235        if should_panic_on_error() {
236            panic!("FORY_PANIC_ON_ERROR: {}", err);
237        }
238        err
239    }
240
241    /// Creates a new [`Error::InvalidData`] from a string or static message.
242    ///
243    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
244    ///
245    /// # Example
246    /// ```
247    /// use fory_core::error::Error;
248    ///
249    /// let err = Error::invalid_data("Invalid data format");
250    /// let err = Error::invalid_data(format!("Invalid data at position {}", 42));
251    /// ```
252    #[inline(always)]
253    #[track_caller]
254    pub fn invalid_data<S: Into<Cow<'static, str>>>(s: S) -> Self {
255        let err = Error::InvalidData(s.into());
256        if should_panic_on_error() {
257            panic!("FORY_PANIC_ON_ERROR: {}", err);
258        }
259        err
260    }
261
262    /// Creates a new [`Error::InvalidRef`] from a string or static message.
263    ///
264    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
265    ///
266    /// # Example
267    /// ```
268    /// use fory_core::error::Error;
269    ///
270    /// let err = Error::invalid_ref("Invalid reference");
271    /// let err = Error::invalid_ref(format!("Invalid ref id {}", 123));
272    /// ```
273    #[inline(always)]
274    #[track_caller]
275    pub fn invalid_ref<S: Into<Cow<'static, str>>>(s: S) -> Self {
276        let err = Error::InvalidRef(s.into());
277        if should_panic_on_error() {
278            panic!("FORY_PANIC_ON_ERROR: {}", err);
279        }
280        err
281    }
282
283    /// Creates a new [`Error::UnknownEnum`] 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::unknown_enum("Unknown enum variant");
292    /// let err = Error::unknown_enum(format!("Unknown variant {}", 5));
293    /// ```
294    #[inline(always)]
295    #[track_caller]
296    pub fn unknown_enum<S: Into<Cow<'static, str>>>(s: S) -> Self {
297        let err = Error::UnknownEnum(s.into());
298        if should_panic_on_error() {
299            panic!("FORY_PANIC_ON_ERROR: {}", err);
300        }
301        err
302    }
303
304    /// Creates a new [`Error::TypeError`] from a string or static message.
305    ///
306    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
307    ///
308    /// # Example
309    /// ```
310    /// use fory_core::error::Error;
311    ///
312    /// let err = Error::type_error("Type error");
313    /// let err = Error::type_error(format!("Expected type {}", "String"));
314    /// ```
315    #[inline(always)]
316    #[track_caller]
317    pub fn type_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
318        let err = Error::TypeError(s.into());
319        if should_panic_on_error() {
320            panic!("FORY_PANIC_ON_ERROR: {}", err);
321        }
322        err
323    }
324
325    /// Creates a new [`Error::EncodingError`] from a string or static message.
326    ///
327    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
328    ///
329    /// # Example
330    /// ```
331    /// use fory_core::error::Error;
332    ///
333    /// let err = Error::encoding_error("Encoding failed");
334    /// let err = Error::encoding_error(format!("Failed to encode as {}", "UTF-8"));
335    /// ```
336    #[inline(always)]
337    #[track_caller]
338    pub fn encoding_error<S: Into<Cow<'static, str>>>(s: S) -> Self {
339        let err = Error::EncodingError(s.into());
340        if should_panic_on_error() {
341            panic!("FORY_PANIC_ON_ERROR: {}", err);
342        }
343        err
344    }
345
346    /// Creates a new [`Error::DepthExceed`] from a string or static message.
347    ///
348    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
349    ///
350    /// # Example
351    /// ```
352    /// use fory_core::error::Error;
353    ///
354    /// let err = Error::depth_exceed("Max depth exceeded");
355    /// let err = Error::depth_exceed(format!("Depth {} exceeds max {}", 100, 64));
356    /// ```
357    #[inline(always)]
358    #[track_caller]
359    pub fn depth_exceed<S: Into<Cow<'static, str>>>(s: S) -> Self {
360        let err = Error::DepthExceed(s.into());
361        if should_panic_on_error() {
362            panic!("FORY_PANIC_ON_ERROR: {}", err);
363        }
364        err
365    }
366
367    /// Creates a new [`Error::Uunsupported`] from a string or static message.
368    ///
369    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
370    ///
371    /// # Example
372    /// ```
373    /// use fory_core::error::Error;
374    ///
375    /// let err = Error::unsupported("Unsupported operation");
376    /// let err = Error::unsupported(format!("Type {} not supported", "MyType"));
377    /// ```
378    #[inline(always)]
379    #[track_caller]
380    pub fn unsupported<S: Into<Cow<'static, str>>>(s: S) -> Self {
381        let err = Error::Uunsupported(s.into());
382        if should_panic_on_error() {
383            panic!("FORY_PANIC_ON_ERROR: {}", err);
384        }
385        err
386    }
387
388    /// Creates a new [`Error::NotAllowed`] from a string or static message.
389    ///
390    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
391    ///
392    /// # Example
393    /// ```
394    /// use fory_core::error::Error;
395    ///
396    /// let err = Error::not_allowed("Operation not allowed");
397    /// let err = Error::not_allowed(format!("Cannot perform {}", "delete"));
398    /// ```
399    #[inline(always)]
400    #[track_caller]
401    pub fn not_allowed<S: Into<Cow<'static, str>>>(s: S) -> Self {
402        let err = Error::NotAllowed(s.into());
403        if should_panic_on_error() {
404            panic!("FORY_PANIC_ON_ERROR: {}", err);
405        }
406        err
407    }
408
409    /// Creates a new [`Error::StructVersionMismatch`] from a string or static message.
410    ///
411    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
412    ///
413    /// # Example
414    /// ```
415    /// use fory_core::error::Error;
416    ///
417    /// let err = Error::struct_version_mismatch("Version mismatch");
418    /// let err = Error::struct_version_mismatch(format!("Class {} version mismatch", "Foo"));
419    /// ```
420    #[inline(always)]
421    #[track_caller]
422    pub fn struct_version_mismatch<S: Into<Cow<'static, str>>>(s: S) -> Self {
423        let err = Error::StructVersionMismatch(s.into());
424        if should_panic_on_error() {
425            panic!("FORY_PANIC_ON_ERROR: {}", err);
426        }
427        err
428    }
429
430    /// Creates a new [`Error::Unknown`] from a string or static message.
431    ///
432    /// This function is a convenient way to produce an error message
433    /// from a literal, `String`, or any type that can be converted into
434    /// a [`Cow<'static, str>`].
435    ///
436    /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message.
437    ///
438    /// # Example
439    /// ```
440    /// use fory_core::error::Error;
441    ///
442    /// let err = Error::unknown("Something went wrong");
443    /// let err = Error::unknown(format!("ID:{} not found", 1));
444    /// ```
445    #[inline(always)]
446    #[track_caller]
447    pub fn unknown<S: Into<Cow<'static, str>>>(s: S) -> Self {
448        let err = Error::Unknown(s.into());
449        if should_panic_on_error() {
450            panic!("FORY_PANIC_ON_ERROR: {}", err);
451        }
452        err
453    }
454}
455
456/// Ensures a condition is true; otherwise returns an [`enum@Error`].
457///
458/// # Examples
459/// ```
460/// use fory_core::ensure;
461/// use fory_core::error::Error;
462///
463/// fn check_value(n: i32) -> Result<(), Error> {
464///     ensure!(n > 0, "value must be positive");
465///     ensure!(n < 10, "value {} too large", n);
466///     Ok(())
467/// }
468/// ```
469#[macro_export]
470macro_rules! ensure {
471    ($cond:expr, $msg:literal) => {
472        if !$cond {
473            return Err($crate::error::Error::unknown($msg));
474        }
475    };
476    ($cond:expr, $err:expr) => {
477        if !$cond {
478            return Err($err);
479        }
480    };
481    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
482        if !$cond {
483            return Err($crate::error::Error::unknown(format!($fmt, $($arg)*)));
484        }
485    };
486}
487
488/// Returns early with an [`enum@Error`].
489///
490/// # Examples
491/// ```
492/// use fory_core::bail;
493/// use fory_core::error::Error;
494///
495/// fn fail_fast() -> Result<(), Error> {
496///     bail!("something went wrong");
497/// }
498/// ```
499#[macro_export]
500macro_rules! bail {
501    ($err:expr) => {
502        return Err($crate::error::Error::unknown($err))
503    };
504    ($fmt:expr, $($arg:tt)*) => {
505        return Err($crate::error::Error::unknown(format!($fmt, $($arg)*)))
506    };
507}
508
509/// Returns early with a [`Error::NotAllowed`].
510///
511/// # Examples
512/// ```
513/// use fory_core::not_allowed;
514/// use fory_core::error::Error;
515///
516/// fn check_operation() -> Result<(), Error> {
517///     not_allowed!("operation not allowed");
518/// }
519///
520/// fn check_operation_with_context(op: &str) -> Result<(), Error> {
521///     not_allowed!("operation {} not allowed", op);
522/// }
523/// ```
524#[macro_export]
525macro_rules! not_allowed {
526    ($err:expr) => {
527        return Err($crate::error::Error::not_allowed($err))
528    };
529    ($fmt:expr, $($arg:tt)*) => {
530        return Err($crate::error::Error::not_allowed(format!($fmt, $($arg)*)))
531    };
532}