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}