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}