11use std:: { error, fmt} ;
22use super :: Kind ;
33
4+ #[ cfg( feature = "serde" ) ]
5+ use serde:: ser:: SerializeStruct ;
6+
47/// The fundamental error type used by this library.
58///
69/// An error type which encapsulates information about whether an error
@@ -23,9 +26,9 @@ use super::Kind;
2326/// ```
2427#[ derive( Debug ) ]
2528pub struct Error {
26- kind : Kind ,
27- error : Box < dyn error:: Error + Send + Sync > ,
28- advice : & ' static [ & ' static str ] ,
29+ pub ( crate ) kind : Kind ,
30+ pub ( crate ) error : Box < dyn error:: Error + Send + Sync > ,
31+ pub ( crate ) advice : & ' static [ & ' static str ] ,
2932}
3033
3134impl Error {
@@ -97,6 +100,52 @@ impl Error {
97100 }
98101 }
99102
103+ /// Gets the advice associated with this error and its causes.
104+ ///
105+ /// Gathers all advice from this error and any causal errors it wraps,
106+ /// returning a deduplicated list of suggestions for how a user should
107+ /// deal with this error.
108+ ///
109+ /// # Examples
110+ /// ```
111+ /// use human_errors;
112+ ///
113+ /// let err = human_errors::wrap_user(
114+ /// human_errors::user(
115+ /// "We could not find a file at /home/user/.config/demo.yml",
116+ /// &["Make sure that the file exists and is readable by the application."]
117+ /// ),
118+ /// "We could not open the config file you provided.",
119+ /// &["Make sure that you've specified a valid config file with the --config option."],
120+ /// );
121+ ///
122+ /// // Prints:
123+ /// // - Make sure that the file exists and is readable by the application.
124+ /// // - Make sure that you've specified a valid config file with the --config option.
125+ /// for tip in err.advice() {
126+ /// println!("- {}", tip);
127+ /// }
128+ /// ``````
129+ pub fn advice ( & self ) -> Vec < & ' static str > {
130+ let mut advice = self . advice . to_vec ( ) ;
131+
132+ let mut cause: Option < & ( dyn std:: error:: Error + ' static ) > = Some ( self . error . as_ref ( ) ) ;
133+ while let Some ( err) = cause {
134+ if let Some ( err) = err. downcast_ref :: < Error > ( ) {
135+ advice. extend_from_slice ( err. advice ) ;
136+ }
137+
138+ cause = err. source ( ) ;
139+ }
140+
141+ advice. reverse ( ) ;
142+
143+ let mut seen = std:: collections:: HashSet :: new ( ) ;
144+ advice. retain ( |item| seen. insert ( * item) ) ;
145+
146+ advice
147+ }
148+
100149 /// Gets the formatted error and its advice.
101150 ///
102151 /// Generates a string containing the description of the error and any causes,
@@ -120,7 +169,7 @@ impl Error {
120169 /// );
121170 ///
122171 /// // Prints a message like the following:
123- /// // Oh no! We could not open the config file you provided.
172+ /// // We could not open the config file you provided. (User error)
124173 /// //
125174 /// // This was caused by:
126175 /// // We could not find a file at /home/user/.config/demo.yml
@@ -176,23 +225,6 @@ impl Error {
176225
177226 causes
178227 }
179-
180- fn advice ( & self ) -> Vec < & ' static str > {
181- let mut advice = self . advice . to_vec ( ) ;
182-
183- let mut cause = self . error . as_ref ( ) ;
184- while let Some ( err) = cause. downcast_ref :: < Error > ( ) {
185- advice. extend_from_slice ( err. advice ) ;
186- cause = err. error . as_ref ( ) ;
187- }
188-
189- advice. reverse ( ) ;
190-
191- let mut seen = std:: collections:: HashSet :: new ( ) ;
192- advice. retain ( |item| seen. insert ( * item) ) ;
193-
194- advice
195- }
196228}
197229
198230impl error:: Error for Error {
@@ -207,6 +239,20 @@ impl fmt::Display for Error {
207239 }
208240}
209241
242+ #[ cfg( feature = "serde" ) ]
243+ impl serde:: Serialize for Error {
244+ fn serialize < S > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error >
245+ where
246+ S : serde:: Serializer ,
247+ {
248+ let mut state = serializer. serialize_struct ( "Error" , 3 ) ?;
249+ state. serialize_field ( "kind" , & self . kind ) ?;
250+ state. serialize_field ( "description" , & self . description ( ) ) ?;
251+ state. serialize_field ( "advice" , & self . advice ( ) ) ?;
252+ state. end ( )
253+ }
254+ }
255+
210256#[ cfg( test) ]
211257mod tests {
212258 use super :: * ;
@@ -242,4 +288,24 @@ mod tests {
242288 "Something bad happened. (System failure)\n \n To try and fix this, you can:\n - Avoid bad things happening in future"
243289 ) ;
244290 }
291+
292+ #[ test]
293+ fn test_advice_aggregation ( ) {
294+ let low_level_err = Error :: new (
295+ "Low-level failure." ,
296+ Kind :: System ,
297+ & [ "Check low-level systems" ] ,
298+ ) ;
299+
300+ let high_level_err = Error :: new (
301+ low_level_err,
302+ Kind :: User ,
303+ & [ "Check high-level configuration" ] ,
304+ ) ;
305+
306+ assert_eq ! (
307+ high_level_err. advice( ) ,
308+ vec![ "Check low-level systems" , "Check high-level configuration" ]
309+ ) ;
310+ }
245311}
0 commit comments