bizerror/
lib.rs

1//! # `BizError` - Structured Business Error Handling for Rust
2//!
3//! A lightweight, flexible business error handling library that provides
4//! structured error codes and contextual information while maintaining full
5//! compatibility with Rust's error ecosystem.
6//!
7//! ## 🎯 Design Philosophy
8//!
9//! **90/10 Principle**: 90% of error handling scenarios only need error codes,
10//! while 10% require detailed context information.
11//!
12//! - **Minimal Core**: `BizError` trait contains only essential business error
13//!   identification
14//! - **Optional Context**: Use `ContextualError` wrapper only when detailed
15//!   context is needed
16//! - **Zero Overhead**: Basic usage scenarios have no additional performance
17//!   cost
18//! - **Full Compatibility**: Seamlessly integrates with thiserror and the
19//!   entire Rust error ecosystem
20//!
21//! ## 🚀 Basic Usage with Derive Macro
22//!
23//! The simplest way to use `BizError` is with the derive macro:
24//!
25//! ```rust
26//! use bizerror::BizError;
27//!
28//! #[derive(BizError, thiserror::Error)]
29//! pub enum ApiError {
30//!     #[bizcode(4001)]
31//!     #[error("Invalid input: {field}")]
32//!     ValidationError { field: String },
33//!
34//!     #[bizcode(8001)]
35//!     #[error("Database connection failed")]
36//!     DatabaseError(#[from] std::io::Error),
37//!
38//!     #[bizcode(8006)]
39//!     #[error("Request timeout")]
40//!     Timeout,
41//! }
42//!
43//! // Use the error
44//! let error = ApiError::ValidationError { field: "email".to_string() };
45//! assert_eq!(error.code(), 4001);
46//! assert_eq!(error.name(), "ValidationError");
47//! assert_eq!(error.to_string(), "Invalid input: email"); // Uses Display implementation
48//! ```
49//!
50//! ## 🏗️ Automatic Code Assignment
51//!
52//! You can configure automatic code assignment for variants without explicit
53//! codes:
54//!
55//! ```rust
56//! use bizerror::BizError;
57//!
58//! #[derive(BizError, thiserror::Error)]
59//! #[bizconfig(auto_start = 1000, auto_increment = 10)]
60//! pub enum ServiceError {
61//!     #[error("Auto-assigned code")]
62//!     AutoError1, // code: 1000
63//!
64//!     #[bizcode(2001)]
65//!     #[error("Explicit code")]
66//!     ExplicitError, // code: 2001
67//!
68//!     #[error("Another auto-assigned")]
69//!     AutoError2, // code: 1010
70//! }
71//! ```
72//!
73//! ## 🔧 Advanced Usage with Context
74//!
75//! For scenarios requiring detailed context information:
76//!
77//! ```rust
78//! use bizerror::*;
79//!
80//! #[derive(BizError, thiserror::Error)]
81//! pub enum ApiError {
82//!     #[bizcode(8001)]
83//!     #[error("Database connection failed")]
84//!     DatabaseError(#[from] std::io::Error),
85//! }
86//!
87//! fn load_user_config() -> Result<String, ContextualError<ApiError>> {
88//!     std::fs::read_to_string("config.json")
89//!         .with_context("Loading user configuration")
90//! }
91//!
92//! # fn example_usage() {
93//! match load_user_config() {
94//!     Ok(config) => println!("Config loaded: {}", config),
95//!     Err(e) => {
96//!         println!("Error code: {}", e.code());
97//!         println!("Context: {}", e.context());
98//!         println!("Location: {}", e.location());
99//!     }
100//! }
101//! # }
102//! ```
103//!
104//! ## 📊 Custom Code Types
105//!
106//! You can use different types for error codes:
107//!
108//! ```rust
109//! use bizerror::BizError;
110//!
111//! // String codes
112//! #[derive(BizError, thiserror::Error)]
113//! #[bizconfig(code_type = "&'static str")]
114//! pub enum StringError {
115//!     #[bizcode("USER_NOT_FOUND")]
116//!     #[error("User not found")]
117//!     UserNotFound,
118//!
119//!     #[error("Auto string code")]
120//!     AutoString, // code: "0"
121//! }
122//!
123//! // Signed integer codes
124//! #[derive(BizError, thiserror::Error)]
125//! #[bizconfig(code_type = "i32", auto_start = -100)]
126//! pub enum SignedError {
127//!     #[error("Negative code")]
128//!     NegativeCode, // code: -100
129//! }
130//! ```
131//!
132//! ## 🎨 Structured Debug Output
133//!
134//! The derive macro automatically generates structured debug output:
135//!
136//! ```rust
137//! # use bizerror::BizError;
138//! # #[derive(BizError, thiserror::Error)]
139//! # pub enum ApiError {
140//! #     #[bizcode(4001)]
141//! #     #[error("Invalid input: {field}")]
142//! #     ValidationError { field: String },
143//! # }
144//! let error = ApiError::ValidationError { field: "email".to_string() };
145//! println!("{:?}", error);
146//! // Output: ApiError { variant: "ValidationError", code: 4001, message: "Invalid input: email" }
147//! ```
148//!
149//! ## 🔗 Error Chains and Context
150//!
151//! Build comprehensive error chains with context:
152//!
153//! ```rust
154//! use bizerror::*;
155//!
156//! #[derive(BizError, thiserror::Error)]
157//! pub enum ServiceError {
158//!     #[bizcode(8001)]
159//!     #[error("Database error: {0}")]
160//!     DatabaseError(#[from] std::io::Error),
161//! }
162//!
163//! fn complex_operation() -> Result<String, ContextualError<ServiceError>> {
164//!     // Multiple layers of context
165//!     std::fs::read_to_string("data.json")
166//!         .with_context("Loading configuration")
167//!         .and_then(|_| {
168//!             std::fs::read_to_string("user.json")
169//!                 .with_context("Loading user data")
170//!         })
171//! }
172//! ```
173//!
174//! ## 🏆 Best Practices
175//!
176//! 1. **Use meaningful error codes**: Group related errors by code ranges
177//!    - 1000-1999: Validation errors
178//!    - 2000-2999: Authentication errors
179//!    - 8000-8999: System errors
180//!
181//! 2. **Leverage automatic assignment**: Use `bizconfig` for consistent code
182//!    spacing
183//!
184//! 3. **Add context sparingly**: Only use `ContextualError` when you need
185//!    detailed debugging
186//!
187//! 4. **Chain errors properly**: Use `#[from]` for automatic conversions
188//!
189//! 5. **Document error codes**: Include code meanings in your API documentation
190
191use core::panic::Location;
192use std::{
193    borrow::Cow,
194    error::Error,
195};
196
197// Re-export the BizError derive macro
198pub use bizerror_impl::BizError;
199
200/// Core business error trait
201///
202/// This trait provides the essential functionality for business error
203/// identification:
204/// - `code()`: Returns a unique business error code
205/// - `name()`: Returns the error type name (typically the enum variant name)
206///
207/// For error messages, use the standard `Display` trait implementation.
208///
209/// ## Example Implementation
210///
211/// ```rust
212/// use std::error::Error;
213///
214/// use bizerror::BizError;
215///
216/// #[derive(Debug)]
217/// pub struct CustomError {
218///     code:    u32,
219///     message: String,
220/// }
221///
222/// impl std::fmt::Display for CustomError {
223///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224///         write!(f, "{}", self.message)
225///     }
226/// }
227///
228/// impl Error for CustomError {}
229///
230/// impl BizError for CustomError {
231///     type CodeType = u32;
232///
233///     fn code(&self) -> Self::CodeType {
234///         self.code
235///     }
236///
237///     fn name(&self) -> &str {
238///         "CustomError"
239///     }
240/// }
241/// ```
242pub trait BizError: Error + Send + Sync + 'static {
243    /// The type of the error code
244    ///
245    /// Can be any type that implements `Copy + Display + Debug + Send + Sync +
246    /// 'static`. Common choices include:
247    /// - `u32` or `u16` for numeric codes
248    /// - `&'static str` for string codes
249    /// - `i32` for signed numeric codes
250    type CodeType: Copy
251        + std::fmt::Display
252        + std::fmt::Debug
253        + Send
254        + Sync
255        + std::hash::Hash
256        + PartialEq
257        + Eq
258        + 'static;
259
260    /// Get the business error code
261    ///
262    /// This should return a unique identifier for this specific error type.
263    /// The code should be consistent across different instances of the same
264    /// error variant.
265    fn code(&self) -> Self::CodeType;
266
267    /// Get the error type name
268    ///
269    /// This typically returns the enum variant name for derived
270    /// implementations. For custom implementations, this should return a
271    /// consistent, descriptive name.
272    fn name(&self) -> &str;
273}
274
275/// Contextual error wrapper (only used when detailed context is needed)
276///
277/// This wrapper allows you to add context information and automatic location
278/// tracking to any `BizError` without changing the original error type.
279///
280/// ## When to Use
281///
282/// Use `ContextualError` when you need:
283/// - Detailed debugging information
284/// - Location tracking for where the error occurred
285/// - Additional context about the operation that failed
286/// - Multiple layers of context in error chains
287///
288/// ## Example
289///
290/// ```rust
291/// use bizerror::*;
292///
293/// #[derive(BizError, thiserror::Error)]
294/// pub enum ServiceError {
295///     #[bizcode(8001)]
296///     #[error("Database connection failed")]
297///     DatabaseError(#[from] std::io::Error),
298/// }
299///
300/// fn load_config() -> Result<String, ContextualError<ServiceError>> {
301///     std::fs::read_to_string("config.json")
302///         .with_context("Loading application configuration")
303/// }
304/// ```
305pub struct ContextualError<E: BizError> {
306    error:    E,
307    context:  Cow<'static, str>, // Avoids allocation for static strings,
308    location: &'static Location<'static>,
309}
310
311impl<E: BizError> ContextualError<E> {
312    /// Create a new contextual error with automatic location tracking
313    ///
314    /// The location is automatically captured using `#[track_caller]`,
315    /// providing precise information about where the error context was added.
316    #[track_caller]
317    pub fn new(error: E, context: impl Into<String>) -> Self {
318        Self {
319            error,
320            context: Cow::Owned(context.into()),
321            location: Location::caller(),
322        }
323    }
324
325    /// Get the original error
326    ///
327    /// This provides access to the underlying `BizError` instance.
328    pub const fn inner(&self) -> &E {
329        &self.error
330    }
331
332    /// Get the context
333    ///
334    /// Returns the contextual information that was added to this error.
335    pub fn context(&self) -> &str {
336        &self.context
337    }
338
339    /// Get the location
340    ///
341    /// Returns the location where the context was added to this error.
342    pub const fn location(&self) -> &'static Location<'static> {
343        self.location
344    }
345
346    /// Add additional context to the existing context
347    ///
348    /// This method appends new context information to the existing context,
349    /// creating a layered context description.
350    ///
351    /// # Example
352    ///
353    /// ```rust
354    /// use bizerror::*;
355    ///
356    /// #[derive(BizError, thiserror::Error)]
357    /// pub enum MyError {
358    ///     #[bizcode(8001)]
359    ///     #[error("IO error")]
360    ///     IoError,
361    /// }
362    ///
363    /// let error = MyError::IoError;
364    /// let contextual = error.with_context("Loading file");
365    /// let layered = contextual.add_context("During startup");
366    /// assert_eq!(layered.context(), "Loading file -> During startup");
367    /// ```
368    #[track_caller]
369    #[must_use]
370    pub fn add_context(self, additional: impl Into<String>) -> Self {
371        let new_context = format!("{} -> {}", self.context, additional.into());
372        Self {
373            error:    self.error,
374            context:  Cow::Owned(new_context),
375            location: Location::caller(),
376        }
377    }
378
379    /// Unwrap the contextual error, returning the inner error
380    ///
381    /// This method consumes the `ContextualError` and returns the underlying
382    /// business error, discarding the context information.
383    ///
384    /// # Example
385    ///
386    /// ```rust
387    /// use bizerror::*;
388    ///
389    /// #[derive(BizError, thiserror::Error)]
390    /// pub enum MyError {
391    ///     #[bizcode(8001)]
392    ///     #[error("IO error")]
393    ///     IoError,
394    /// }
395    ///
396    /// let error = MyError::IoError;
397    /// let contextual = error.with_context("Some context");
398    /// let original = contextual.into_inner();
399    /// // original is now MyError::IoError again
400    /// ```
401    pub fn into_inner(self) -> E {
402        self.error
403    }
404
405    /// Find the first error in the chain of a specific type
406    ///
407    /// This method traverses the error chain and returns the first error
408    /// of the specified type. Useful for extracting specific error types
409    /// from a complex error chain.
410    ///
411    /// # Example
412    ///
413    /// ```rust
414    /// use std::io;
415    ///
416    /// use bizerror::*;
417    ///
418    /// #[derive(BizError, thiserror::Error)]
419    /// pub enum MyError {
420    ///     #[bizcode(8001)]
421    ///     #[error("IO error: {0}")]
422    ///     IoError(#[from] io::Error),
423    /// }
424    ///
425    /// let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
426    /// let my_error = MyError::IoError(io_error);
427    /// let contextual = my_error.with_context("Loading config");
428    ///
429    /// // Find the original io::Error in the chain
430    /// let found_io_error = contextual.find_root::<io::Error>();
431    /// assert!(found_io_error.is_some());
432    /// ```
433    pub fn find_root<T>(&self) -> Option<&T>
434    where
435        T: Error + 'static,
436    {
437        let mut current: &dyn Error = self;
438        while let Some(source) = current.source() {
439            if let Some(target) = source.downcast_ref::<T>() {
440                return Some(target);
441            }
442            current = source;
443        }
444        None
445    }
446
447    /// Count the depth of the error chain
448    ///
449    /// Returns the number of errors in the chain, including this error.
450    /// Useful for understanding the complexity of error propagation.
451    ///
452    /// # Example
453    ///
454    /// ```rust
455    /// use bizerror::*;
456    /// use std::io;
457    ///
458    /// #[derive(BizError, thiserror::Error)]
459    /// pub enum MyError {
460    ///     #[bizcode(8001)]
461    ///     #[error("IO error: {0}")]
462    ///     IoError(#[from] io::Error),
463    /// }
464    ///
465    /// let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
466    /// let my_error = MyError::IoError(io_error);
467    /// let contextual = my_error.with_context("Loading config");
468    ///
469    /// assert_eq!(contextual.chain_depth(), 3); // ContextualError -> MyError -> io::Error
470    /// ```
471    pub fn chain_depth(&self) -> usize {
472        let mut depth = 1;
473        let mut current: &dyn Error = self;
474        while let Some(source) = current.source() {
475            depth += 1;
476            current = source;
477        }
478        depth
479    }
480
481    /// Get the root cause message of the error chain
482    ///
483    /// Returns the deepest error message in the chain, which is typically the
484    /// original cause of the error.
485    ///
486    /// # Example
487    ///
488    /// ```rust
489    /// use std::io;
490    ///
491    /// use bizerror::*;
492    ///
493    /// #[derive(BizError, thiserror::Error)]
494    /// pub enum MyError {
495    ///     #[bizcode(8001)]
496    ///     #[error("IO error: {0}")]
497    ///     IoError(#[from] io::Error),
498    /// }
499    ///
500    /// let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
501    /// let my_error = MyError::IoError(io_error);
502    /// let contextual = my_error.with_context("Loading config");
503    ///
504    /// let root_cause = contextual.root_cause_message();
505    /// assert_eq!(root_cause, "file not found");
506    /// ```
507    pub fn root_cause_message(&self) -> String {
508        let mut current: &dyn Error = self;
509        while let Some(source) = current.source() {
510            current = source;
511        }
512        current.to_string()
513    }
514
515    /// Collect all error messages in the chain
516    ///
517    /// Returns a vector of all error messages in the chain, from the current
518    /// error to the root cause. Useful for comprehensive error reporting.
519    ///
520    /// # Example
521    ///
522    /// ```rust
523    /// use std::io;
524    ///
525    /// use bizerror::*;
526    ///
527    /// #[derive(BizError, thiserror::Error)]
528    /// pub enum MyError {
529    ///     #[bizcode(8001)]
530    ///     #[error("IO error: {0}")]
531    ///     IoError(#[from] io::Error),
532    /// }
533    ///
534    /// let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
535    /// let my_error = MyError::IoError(io_error);
536    /// let contextual = my_error.with_context("Loading config");
537    ///
538    /// let chain = contextual.error_chain_messages();
539    /// assert_eq!(chain.len(), 3);
540    /// ```
541    pub fn error_chain_messages(&self) -> Vec<String> {
542        let mut chain = vec![self.to_string()];
543        let mut current = self.source();
544        while let Some(source) = current {
545            chain.push(source.to_string());
546            current = source.source();
547        }
548        chain
549    }
550
551    /// Check if the error chain contains a specific error type
552    ///
553    /// Returns true if any error in the chain is of the specified type.
554    /// Useful for conditional error handling.
555    ///
556    /// # Example
557    ///
558    /// ```rust
559    /// use std::io;
560    ///
561    /// use bizerror::*;
562    ///
563    /// #[derive(BizError, thiserror::Error)]
564    /// pub enum MyError {
565    ///     #[bizcode(8001)]
566    ///     #[error("IO error: {0}")]
567    ///     IoError(#[from] io::Error),
568    /// }
569    ///
570    /// let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
571    /// let my_error = MyError::IoError(io_error);
572    /// let contextual = my_error.with_context("Loading config");
573    ///
574    /// assert!(contextual.contains_error::<io::Error>());
575    /// assert!(!contextual.contains_error::<std::fmt::Error>());
576    /// ```
577    pub fn contains_error<T>(&self) -> bool
578    where
579        T: Error + 'static,
580    {
581        self.find_root::<T>().is_some()
582    }
583
584    /// Check if the error chain contains a specific business error code
585    ///
586    /// Returns true if any `BizError` in the chain has the specified code.
587    /// Useful for conditional error handling based on business error codes.
588    ///
589    /// # Example
590    ///
591    /// ```rust
592    /// use bizerror::*;
593    ///
594    /// #[derive(BizError, thiserror::Error)]
595    /// pub enum MyError {
596    ///     #[bizcode(8001)]
597    ///     #[error("IO error")]
598    ///     IoError,
599    ///
600    ///     #[bizcode(8002)]
601    ///     #[error("Network error")]
602    ///     NetworkError,
603    /// }
604    ///
605    /// let error = MyError::IoError;
606    /// let contextual = error.with_context("Operation failed");
607    ///
608    /// assert!(contextual.chain_contains_code(8001));
609    /// assert!(!contextual.chain_contains_code(8002));
610    /// ```
611    pub fn chain_contains_code<C>(&self, code: C) -> bool
612    where
613        C: PartialEq<E::CodeType> + Copy,
614    {
615        let mut current: &dyn Error = self;
616        loop {
617            if let Some(biz_error) = current.downcast_ref::<E>() &&
618                code == biz_error.code()
619            {
620                return true;
621            }
622            if let Some(contextual) = current.downcast_ref::<Self>() &&
623                code == contextual.error.code()
624            {
625                return true;
626            }
627            if let Some(source) = current.source() {
628                current = source;
629            } else {
630                break;
631            }
632        }
633        false
634    }
635}
636
637impl<E: BizError> std::fmt::Debug for ContextualError<E> {
638    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
639        f.debug_struct("ContextualError")
640            .field("type", &self.error.name())
641            .field("code", &self.error.code())
642            .field("message", &self.error.to_string())
643            .field("context", &self.context.as_ref())
644            .field(
645                "location",
646                &format!(
647                    "{}:{}:{}",
648                    self.location.file(),
649                    self.location.line(),
650                    self.location.column()
651                ),
652            )
653            .finish()
654    }
655}
656
657impl<E: BizError> std::fmt::Display for ContextualError<E> {
658    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
659        write!(f, "{}\nContext: {}", self.error, self.context)
660    }
661}
662
663impl<E: BizError> Error for ContextualError<E> {
664    fn source(&self) -> Option<&(dyn Error + 'static)> {
665        Some(&self.error)
666    }
667}
668
669impl<E: BizError> BizError for ContextualError<E> {
670    type CodeType = E::CodeType;
671
672    fn code(&self) -> Self::CodeType {
673        self.error.code()
674    }
675
676    fn name(&self) -> &str {
677        self.error.name()
678    }
679}
680
681/// Result extension trait (simplified)
682///
683/// Provides convenient methods to add business context to any Result.
684/// This trait is automatically implemented for all `Result<T, E>` types
685/// where `E` implements `Error`.
686///
687/// ## Core Methods
688///
689/// - `with_context()` - Add context and convert to `ContextualError`
690/// - `map_biz()` - Simple error type conversion
691/// - `with_context_if()` - Conditional context addition
692///
693/// ## Example
694///
695/// ```rust
696/// use bizerror::*;
697///
698/// #[derive(BizError, thiserror::Error)]
699/// pub enum MyError {
700///     #[bizcode(8001)]
701///     #[error("IO error: {0}")]
702///     IoError(#[from] std::io::Error),
703/// }
704///
705/// fn read_file() -> Result<String, ContextualError<MyError>> {
706///     std::fs::read_to_string("important.txt")
707///         .with_context("Reading critical configuration file")
708/// }
709/// ```
710pub trait ResultExt<T, E> {
711    /// Add contextual information and convert to `ContextualError`
712    ///
713    /// This method allows you to add context to any `Result` that contains
714    /// an error that can be converted to your business error type.
715    ///
716    /// The context is captured with automatic location tracking.
717    fn with_context<B>(
718        self,
719        context: impl Into<String>,
720    ) -> Result<T, ContextualError<B>>
721    where
722        B: BizError + From<E>;
723
724    /// Convert error type without adding context
725    ///
726    /// This is a convenience method that converts the error type to a business
727    /// error. It's equivalent to `.map_err(B::from)`.
728    fn map_biz<B>(self) -> Result<T, B>
729    where
730        B: BizError + From<E>;
731
732    /// Add context conditionally
733    ///
734    /// This method adds context only when the condition is true.
735    /// If the condition is false, it still converts the error type but without
736    /// context.
737    fn with_context_if<B>(
738        self,
739        condition: bool,
740        context: impl Into<String>,
741    ) -> Result<T, ContextualError<B>>
742    where
743        B: BizError + From<E>;
744
745    /// Chain operations with error conversion
746    ///
747    /// This method allows you to chain operations while converting errors
748    /// to business error types. It's a convenience method that combines
749    /// `and_then` with automatic error type conversion.
750    ///
751    /// # Example
752    ///
753    /// ```rust
754    /// use bizerror::*;
755    ///
756    /// #[derive(BizError, thiserror::Error)]
757    /// pub enum MyError {
758    ///     #[bizcode(8001)]
759    ///     #[error("IO error: {0}")]
760    ///     IoError(#[from] std::io::Error),
761    /// }
762    ///
763    /// let initial_err: Result<u32, std::io::Error> = Err(std::io::Error::new(
764    ///     std::io::ErrorKind::BrokenPipe,
765    ///     "pipe broken",
766    /// ));
767    /// let chained_err: Result<String, MyError> =
768    ///     initial_err.and_then_biz(|val| Ok(format!("Value is {val}")));
769    /// assert!(chained_err.is_err()); // true
770    /// ```
771    fn and_then_biz<U, F, B>(self, f: F) -> Result<U, B>
772    where
773        F: FnOnce(T) -> Result<U, B>,
774        B: BizError + From<E>;
775}
776
777impl<T, E: Error + 'static> ResultExt<T, E> for Result<T, E> {
778    #[track_caller]
779    fn with_context<B>(
780        self,
781        context: impl Into<String>,
782    ) -> Result<T, ContextualError<B>>
783    where
784        B: BizError + From<E>,
785    {
786        self.map_err(|e| ContextualError::new(B::from(e), context))
787    }
788
789    fn map_biz<B>(self) -> Result<T, B>
790    where
791        B: BizError + From<E>,
792    {
793        self.map_err(|e| B::from(e))
794    }
795
796    fn with_context_if<B>(
797        self,
798        condition: bool,
799        context: impl Into<String>,
800    ) -> Result<T, ContextualError<B>>
801    where
802        B: BizError + From<E>,
803    {
804        if condition {
805            self.with_context(context)
806        } else {
807            self.map_err(|e| {
808                ContextualError::new(B::from(e), "no context".to_string())
809            })
810        }
811    }
812
813    fn and_then_biz<U, F, B>(self, f: F) -> Result<U, B>
814    where
815        F: FnOnce(T) -> Result<U, B>,
816        B: BizError + From<E>,
817    {
818        match self {
819            Ok(t) => f(t),
820            Err(e) => Err(B::from(e)),
821        }
822    }
823}
824
825/// `BizError` extension trait
826///
827/// Provides convenient methods for adding context to business errors.
828/// This trait is automatically implemented for all types that implement
829/// `BizError`.
830///
831/// ## Example
832///
833/// ```rust
834/// use bizerror::*;
835///
836/// #[derive(BizError, thiserror::Error)]
837/// pub enum ApiError {
838///     #[bizcode(4001)]
839///     #[error("Validation failed")]
840///     ValidationError,
841/// }
842///
843/// let error = ApiError::ValidationError;
844/// let contextual = error.with_context("Processing user registration");
845/// ```
846pub trait BizErrorExt: BizError + Sized {
847    /// Add context with automatic location tracking
848    ///
849    /// This method wraps the error in a `ContextualError` with the provided
850    /// context and automatic location tracking.
851    #[track_caller]
852    fn with_context(self, context: impl Into<String>) -> ContextualError<Self> {
853        ContextualError::new(self, context)
854    }
855}
856
857impl<T: BizError> BizErrorExt for T {}
858
859/// Option extension trait
860///
861/// Provides convenient methods to convert `Option` to `Result` with business
862/// errors. This trait is automatically implemented for all `Option<T>` types.
863///
864/// ## Example
865///
866/// ```rust
867/// use bizerror::*;
868///
869/// #[derive(BizError, thiserror::Error)]
870/// pub enum MyError {
871///     #[bizcode(4001)]
872///     #[error("Value not found")]
873///     NotFound,
874/// }
875///
876/// let value: Option<String> = None;
877/// let result = value.ok_or_biz(MyError::NotFound);
878/// assert!(result.is_err());
879/// ```
880pub trait OptionExt<T> {
881    /// Convert `Option<T>` to `Result<T, B>` with a business error
882    ///
883    /// This is a convenience method that converts `None` to an error
884    /// of the specified business error type.
885    ///
886    /// # Example
887    ///
888    /// ```rust
889    /// use bizerror::*;
890    ///
891    /// #[derive(BizError, thiserror::Error)]
892    /// pub enum MyError {
893    ///     #[bizcode(4001)]
894    ///     #[error("User not found")]
895    ///     UserNotFound,
896    /// }
897    ///
898    /// fn find_user(id: u32) -> Option<String> {
899    ///     None // simulate not found
900    /// }
901    ///
902    /// let result = find_user(123).ok_or_biz(MyError::UserNotFound);
903    /// assert!(result.is_err());
904    /// assert_eq!(result.unwrap_err().code(), 4001);
905    /// ```
906    fn ok_or_biz<B>(self, error: B) -> Result<T, B>
907    where
908        B: BizError;
909}
910
911impl<T> OptionExt<T> for Option<T> {
912    fn ok_or_biz<B>(self, error: B) -> Result<T, B>
913    where
914        B: BizError,
915    {
916        self.ok_or(error)
917    }
918}
919
920/// Business errors collection for aggregating multiple errors
921///
922/// This type is useful for scenarios where you need to collect all errors
923/// instead of failing on the first one, such as form validation or batch
924/// processing.
925///
926/// ## Example
927///
928/// ```rust
929/// use bizerror::*;
930///
931/// #[derive(BizError, thiserror::Error)]
932/// pub enum ValidationError {
933///     #[bizcode(4001)]
934///     #[error("Invalid email: {email}")]
935///     InvalidEmail { email: String },
936///
937///     #[bizcode(4002)]
938///     #[error("Password too short")]
939///     PasswordTooShort,
940/// }
941///
942/// fn validate_user(
943///     email: &str,
944///     password: &str,
945/// ) -> Result<(), BizErrors<ValidationError>> {
946///     let mut errors = BizErrors::new();
947///
948///     if !email.contains('@') {
949///         errors.push_simple(ValidationError::InvalidEmail {
950///             email: email.to_string(),
951///         });
952///     }
953///
954///     if password.len() < 8 {
955///         errors.push_simple(ValidationError::PasswordTooShort);
956///     }
957///
958///     if errors.is_empty() {
959///         Ok(())
960///     } else {
961///         Err(errors)
962///     }
963/// }
964/// ```
965pub struct BizErrors<E: BizError> {
966    errors: Vec<ContextualError<E>>,
967}
968
969impl<E: BizError> BizErrors<E> {
970    /// Create a new empty error collection
971    pub const fn new() -> Self {
972        Self { errors: Vec::new() }
973    }
974
975    /// Create a new error collection with the given capacity
976    pub fn with_capacity(capacity: usize) -> Self {
977        Self {
978            errors: Vec::with_capacity(capacity),
979        }
980    }
981
982    /// Add a contextual error to the collection
983    pub fn push(&mut self, error: ContextualError<E>) {
984        self.errors.push(error);
985    }
986
987    /// Add a simple business error to the collection
988    ///
989    /// The error will be wrapped in a `ContextualError` with minimal context.
990    #[track_caller]
991    pub fn push_simple(&mut self, error: E) {
992        self.errors.push(ContextualError::new(error, ""));
993    }
994
995    /// Add a business error with context to the collection
996    #[track_caller]
997    pub fn push_with_context(&mut self, error: E, context: impl Into<String>) {
998        self.errors.push(ContextualError::new(error, context));
999    }
1000
1001    /// Get the number of errors in the collection
1002    pub const fn len(&self) -> usize {
1003        self.errors.len()
1004    }
1005
1006    /// Check if the error collection is empty
1007    pub const fn is_empty(&self) -> bool {
1008        self.errors.is_empty()
1009    }
1010
1011    /// Get an iterator over the errors
1012    pub fn iter(&self) -> impl Iterator<Item = &ContextualError<E>> {
1013        self.errors.iter()
1014    }
1015
1016    /// Get a reference to the errors vector
1017    pub fn as_slice(&self) -> &[ContextualError<E>] {
1018        &self.errors
1019    }
1020
1021    /// Convert into the underlying errors vector
1022    pub fn into_vec(self) -> Vec<ContextualError<E>> {
1023        self.errors
1024    }
1025
1026    /// Get the first error in the collection
1027    pub fn first(&self) -> Option<&ContextualError<E>> {
1028        self.errors.first()
1029    }
1030
1031    /// Get the last error in the collection
1032    pub fn last(&self) -> Option<&ContextualError<E>> {
1033        self.errors.last()
1034    }
1035
1036    /// Collect successful results and errors from an iterator
1037    ///
1038    /// Returns a tuple containing all successful values and optionally
1039    /// the collected errors (if any occurred).
1040    ///
1041    /// # Example
1042    ///
1043    /// ```rust
1044    /// use bizerror::*;
1045    ///
1046    /// #[derive(BizError, thiserror::Error)]
1047    /// pub enum ProcessError {
1048    ///     #[bizcode(5001)]
1049    ///     #[error("Invalid value: {value}")]
1050    ///     InvalidValue { value: i32 },
1051    /// }
1052    ///
1053    /// let results: Vec<Result<i32, ContextualError<ProcessError>>> = vec![
1054    ///     Ok(1),
1055    ///     Ok(2),
1056    ///     Err(ProcessError::InvalidValue { value: 3 }
1057    ///         .with_context("Processing item 3")),
1058    ///     Ok(4),
1059    ///     Err(ProcessError::InvalidValue { value: 5 }
1060    ///         .with_context("Processing item 5")),
1061    /// ];
1062    ///
1063    /// let (successes, errors) = BizErrors::collect_from(results.into_iter());
1064    /// assert_eq!(successes, vec![1, 2, 4]);
1065    /// assert!(errors.is_some());
1066    /// assert_eq!(errors.unwrap().len(), 2);
1067    /// ```
1068    pub fn collect_from<T, I>(iter: I) -> (Vec<T>, Option<Self>)
1069    where
1070        I: Iterator<Item = Result<T, ContextualError<E>>>,
1071    {
1072        let mut successes = Vec::new();
1073        let mut errors = Self::new();
1074
1075        for result in iter {
1076            match result {
1077                Ok(value) => successes.push(value),
1078                Err(error) => errors.push(error),
1079            }
1080        }
1081
1082        let errors = if errors.is_empty() {
1083            None
1084        } else {
1085            Some(errors)
1086        };
1087
1088        (successes, errors)
1089    }
1090
1091    /// Collect all errors from an iterator of Results
1092    ///
1093    /// Returns `None` if no errors occurred, or `Some(BizErrors)` with all
1094    /// errors.
1095    ///
1096    /// # Example
1097    ///
1098    /// ```rust
1099    /// use bizerror::*;
1100    ///
1101    /// #[derive(BizError, thiserror::Error)]
1102    /// pub enum ValidationError {
1103    ///     #[bizcode(4001)]
1104    ///     #[error("Invalid field")]
1105    ///     InvalidField,
1106    /// }
1107    ///
1108    /// let results: Vec<Result<(), ContextualError<ValidationError>>> = vec![
1109    ///     Ok(()),
1110    ///     Err(ValidationError::InvalidField.with_context("Field 1")),
1111    ///     Err(ValidationError::InvalidField.with_context("Field 2")),
1112    /// ];
1113    ///
1114    /// let errors = BizErrors::collect_errors(results.into_iter());
1115    /// assert!(errors.is_some());
1116    /// assert_eq!(errors.unwrap().len(), 2);
1117    /// ```
1118    pub fn collect_errors<T, I>(iter: I) -> Option<Self>
1119    where
1120        I: Iterator<Item = Result<T, ContextualError<E>>>,
1121    {
1122        let mut errors = Self::new();
1123
1124        for result in iter {
1125            if let Err(error) = result {
1126                errors.push(error);
1127            }
1128        }
1129
1130        if errors.is_empty() {
1131            None
1132        } else {
1133            Some(errors)
1134        }
1135    }
1136
1137    /// Check if any error in the collection has the specified code
1138    pub fn contains_code<C>(&self, code: C) -> bool
1139    where
1140        C: PartialEq<E::CodeType> + Copy,
1141    {
1142        self.errors.iter().any(|error| code == error.code())
1143    }
1144
1145    /// Get all unique error codes in the collection
1146    pub fn error_codes(&self) -> Vec<E::CodeType> {
1147        let mut codes: Vec<E::CodeType> =
1148            self.errors.iter().map(BizError::code).collect();
1149        codes.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
1150        codes.dedup();
1151        codes
1152    }
1153
1154    /// Filter errors by a predicate
1155    ///
1156    /// Returns an iterator over the errors that satisfy the given predicate.
1157    ///
1158    /// # Example
1159    ///
1160    /// ```rust
1161    /// use bizerror::*;
1162    ///
1163    /// #[derive(BizError, thiserror::Error)]
1164    /// pub enum MyError {
1165    ///     #[bizcode(4001)]
1166    ///     #[error("Validation error")]
1167    ///     ValidationError,
1168    ///
1169    ///     #[bizcode(8001)]
1170    ///     #[error("System error")]
1171    ///     SystemError,
1172    /// }
1173    ///
1174    /// let mut errors = BizErrors::new();
1175    /// errors.push_simple(MyError::ValidationError);
1176    /// errors.push_simple(MyError::SystemError);
1177    ///
1178    /// // Filter only validation errors (4xxx codes)
1179    /// let validation_errors: Vec<_> = errors
1180    ///     .filter(|e| e.code() >= 4000 && e.code() < 5000)
1181    ///     .collect();
1182    /// assert_eq!(validation_errors.len(), 1);
1183    /// ```
1184    pub fn filter<F>(
1185        &self,
1186        predicate: F,
1187    ) -> impl Iterator<Item = &ContextualError<E>>
1188    where
1189        F: Fn(&ContextualError<E>) -> bool,
1190    {
1191        self.errors.iter().filter(move |e| predicate(*e))
1192    }
1193}
1194
1195impl<E: BizError> Default for BizErrors<E> {
1196    fn default() -> Self {
1197        Self::new()
1198    }
1199}
1200
1201impl<E: BizError> IntoIterator for BizErrors<E> {
1202    type Item = ContextualError<E>;
1203    type IntoIter = std::vec::IntoIter<Self::Item>;
1204    fn into_iter(self) -> Self::IntoIter {
1205        self.errors.into_iter()
1206    }
1207}
1208
1209impl<E: BizError> std::fmt::Debug for BizErrors<E> {
1210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1211        if self.errors.is_empty() {
1212            f.debug_struct("BizErrors").field("count", &0).finish()
1213        } else if self.errors.len() == 1 {
1214            f.debug_struct("BizErrors")
1215                .field("count", &1)
1216                .field("error", &self.errors[0])
1217                .finish()
1218        } else {
1219            let mut debug_struct = f.debug_struct("BizErrors");
1220            debug_struct.field("count", &self.errors.len());
1221
1222            let codes: Vec<_> =
1223                self.errors.iter().map(BizError::code).collect();
1224            debug_struct.field("codes", &codes);
1225
1226            // Show first few errors for detailed view
1227            if self.errors.len() <= 3 {
1228                debug_struct.field("errors", &self.errors);
1229            } else {
1230                debug_struct.field("first_3_errors", &&self.errors[0..3]);
1231                debug_struct.field(
1232                    "note",
1233                    &format!("... and {} more", self.errors.len() - 3),
1234                );
1235            }
1236
1237            debug_struct.finish()
1238        }
1239    }
1240}
1241
1242impl<E: BizError> std::fmt::Display for BizErrors<E> {
1243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1244        if self.errors.is_empty() {
1245            write!(f, "No errors")
1246        } else if self.errors.len() == 1 {
1247            write!(f, "{}", self.errors[0])
1248        } else {
1249            writeln!(
1250                f,
1251                "Multiple errors occurred ({} total):",
1252                self.errors.len()
1253            )?;
1254            for (i, error) in self.errors.iter().enumerate() {
1255                writeln!(f, "  {}. {}", i + 1, error)?;
1256            }
1257            Ok(())
1258        }
1259    }
1260}
1261
1262impl<E: BizError> Error for BizErrors<E> {
1263    fn source(&self) -> Option<&(dyn Error + 'static)> {
1264        // Return the first error as the source
1265        self.errors.first().map(|e| e as &dyn Error)
1266    }
1267}
1268
1269impl<E: BizError> BizError for BizErrors<E> {
1270    type CodeType = E::CodeType;
1271
1272    fn code(&self) -> Self::CodeType {
1273        // Return the code of the first error
1274        self.errors
1275            .first()
1276            .map_or_else(|| panic!("BizErrors is empty"), BizError::code)
1277    }
1278
1279    fn name(&self) -> &'static str {
1280        "BizErrors"
1281    }
1282}
1283
1284impl<'a, E: BizError> IntoIterator for &'a BizErrors<E> {
1285    type Item = &'a ContextualError<E>;
1286    type IntoIter = std::slice::Iter<'a, ContextualError<E>>;
1287
1288    fn into_iter(self) -> Self::IntoIter {
1289        self.errors.iter()
1290    }
1291}
1292
1293// Allow collecting Results into BizErrors
1294impl<E: BizError> FromIterator<ContextualError<E>> for BizErrors<E> {
1295    fn from_iter<T: IntoIterator<Item = ContextualError<E>>>(iter: T) -> Self {
1296        Self {
1297            errors: iter.into_iter().collect(),
1298        }
1299    }
1300}
1301
1302impl<E: BizError> FromIterator<E> for BizErrors<E> {
1303    #[track_caller]
1304    fn from_iter<T: IntoIterator<Item = E>>(iter: T) -> Self {
1305        Self {
1306            errors: iter
1307                .into_iter()
1308                .map(|e| ContextualError::new(e, ""))
1309                .collect(),
1310        }
1311    }
1312}