nuhound/
lib.rs

1//! A Rust library for enhanced error tracking
2//! 
3//! Rust programmers often find the question mark operator invaluable in extracting values from
4//! Result and Option and immediately returning to the calling context in the case of an Err or
5//! None. This crate provides some enhancements to this functionality by:
6//! - Converting Result::Err and Option::None values to a single nuhound type error;
7//! - Creating an error chain that can help pinpoint the source of the error;
8//! - Providing a `disclose` feature that enhances error messages by including the filename, line
9//! number and column number of the source file that caused the error. This functionality is
10//! provided by the `here!`, `convert!`, `examine!` and `custom!` macros when the `disclose`
11//! feature is enabled;
12//! - Simplifying error handling in a concise and consistent Rust style.
13//! - Providing a simple implementation that requires minimal changes to your coding experience.
14//! 
15//! Remember to add this to Cargo.toml:
16//! ```text
17//! [features]
18//! ## To help diagnose errors, use the disclose feature when compiling.
19//! ## This ensures that the source file name and line number are displayed
20//! ## when using the here!, convert!, examine! and custom! macros.
21//! ## example usage: cargo build --features=disclose
22//! disclose = []
23//! ```
24//! ## Examples
25//!
26//! ### here!
27//! 
28//! The following example shows how the `here` macro is used to report an error but still retain
29//! the underlying error or errors that can be displayed using the `trace` method.
30//! ```
31//! use nuhound::{Report, here, ResultExtension};
32//! 
33//! fn generate_error() -> Report<u32> {
34//!     let text = "NaN";
35//!     let value = text.parse::<u32>().report(|e| here!(e, "Oh dear - '{}' could not be \
36//!     converted to an integer", text))?;
37//!     Ok(value)
38//! }
39//! 
40//! let result = generate_error();
41//! 
42//! match result {
43//!     Ok(_) => unreachable!(),
44//!     Err(e) => {
45//!         #[cfg(feature = "disclose")]
46//!         eprintln!("{}", e.trace());
47//!         #[cfg(not(feature = "disclose"))]
48//!         eprintln!("{}", e);
49//!     },
50//! }
51//! // With the disclose feature enabled the code will emit:
52//! // 0: src/main.rs:6:48: Oh dear - 'NaN' could not be converted to an integer
53//! // 1: invalid digit found in string
54//! //
55//! // With the disclose feature disabled the code will emit:
56//! // Oh dear - 'NaN' could not be converted to an integer
57//! ```
58//!
59//! ### convert! and examine!
60//!
61//! The following example shows how the `convert` and `examine` macros are used to simplify error
62//! tracing. This is achieved by encapsulating rust methods and functions that return values in the form of
63//! `Result<T, E>`.  Using these macros affords the same error handling capabilities as the `here`
64//! macro but in a more compact form.
65//!
66//! Notice that the `convert` macro is used to translate the error produced by `text.parse` into a
67//! `nuhound` error. The `examine` macro is used when the code generating an error is already a
68//! `nuhound` type. It would, however, be possible to replace the `examine` macros in this example with
69//! `convert`, but it is more code efficient to use `examine` whenever possible.
70//! ```
71//! use nuhound::{Report, ResultExtension, examine, convert};
72//!
73//! fn my_result() -> Report<()> {
74//!     let text = "NaN";
75//!     let _value = convert!(text.parse::<u32>(), "Oh dear - '{}' could not be \
76//!     converted to an integer", text)?;
77//!     Ok(())
78//! }
79//!
80//! fn layer2() -> Report<()> {
81//!     let _result = examine!(my_result(), "Layer 2 failure")?;
82//!     Ok(())
83//! }
84//!
85//! fn layer1() -> Report<()> {
86//!     let _result = examine!(layer2(), "Layer 1 failure")?;
87//!     Ok(())
88//! }
89//!
90//! match layer1() {
91//!     Ok(_) => unreachable!(),
92//!     Err(e) => {
93//!         #[cfg(feature = "disclose")]
94//!         eprintln!("{}", e.trace());
95//!         #[cfg(not(feature = "disclose"))]
96//!         eprintln!("{}", e);
97//!     },
98//! }
99//! // With the disclose feature enabled the code will emit:
100//! // 0: src/main.rs:16:23: Layer 1 failure
101//! // 1: src/main.rs:11:23: Layer 2 failure
102//! // 2: src/main.rs:6:22: Oh dear - 'NaN' could not be converted to an integer
103//! // 3: invalid digit found in string
104//! //
105//! // With the disclose feature disabled the code will emit:
106//! // Layer 1 failure
107//! ```
108//!
109//! ### custom!
110//!
111//! This example shows how the `custom!` macro could be used to generate an error based on a
112//! conditional branch
113//! ```
114//! use nuhound::{Report, ResultExtension, examine, custom};
115//!
116//! fn my_custom() -> Report<()> {
117//!     let reason = "No reason at all";
118//!     if reason != "" {
119//!         custom!("This just fails because of: {}", reason)
120//!     } else {
121//!         Ok(())
122//!     }
123//! }
124//!
125//! fn layer2() -> Report<()> {
126//!     let _result = examine!(my_custom(), "Layer 2 failure")?;
127//!     Ok(())
128//! }
129//!
130//! fn layer1() -> Report<()> {
131//!     let _result = examine!(layer2(), "Top level failure")?;
132//!     Ok(())
133//! }
134//!
135//! match layer1() {
136//!     Ok(_) => unreachable!(),
137//!     Err(e) => {
138//!         #[cfg(feature = "disclose")]
139//!         eprintln!("{}", e.trace());
140//!         #[cfg(not(feature = "disclose"))]
141//!         eprintln!("{}", e);
142//!     },
143//! }
144//! // With the disclose feature enabled the code will emit:
145//! // 0: src/main.rs:19:23: Top level failure
146//! // 1: src/main.rs:14:23: Layer 2 failure
147//! // 2: src/main.rs:7:13: This just fails because of: No reason at all
148//! //
149//! // With the disclose feature disabled the code will emit:
150//! // Top level failure
151//! ```
152//!
153//! ### Option handling
154//!
155//! The `convert!` macro can be used with an Option to handle 'None' as a type of error. In this
156//! example we attempt to get a value from a vector with an out-of-range index. The 'get' will
157//! return a None value that is handled by the `convert!` macro.
158//! ```
159//! use nuhound::{Report, ResultExtension, OptionExtension, examine, convert};
160//!
161//! fn my_option() -> Report<()> {
162//!     let vector = vec![0,1,2,3];
163//!     let index = 4;
164//!     let value = convert!(vector.get(index), "Index {index} is out of range")?;
165//!     println!("Value = {value}");
166//!     Ok(())
167//! }
168//!
169//! fn layer2() -> Report<()> {
170//!     let _result = examine!(my_option(), "Layer 2 failure")?;
171//!     Ok(())
172//! }
173//!
174//! fn layer1() -> Report<()> {
175//!     let _result = examine!(layer2(), "Top level failure")?;
176//!     Ok(())
177//! }
178//!
179//! match layer1() {
180//!     Ok(_) => unreachable!(),
181//!     Err(e) => {
182//!         #[cfg(feature = "disclose")]
183//!         eprintln!("{}", e.trace());
184//!         #[cfg(not(feature = "disclose"))]
185//!         eprintln!("{}", e);
186//!     },
187//! }
188//! // With the disclose feature enabled the code will emit:
189//! // 0: src/main.rs:18:23: Top level failure
190//! // 1: src/main.rs:13:23: Layer 2 failure
191//! // 2: src/main.rs:7:21: Index 4 is out of range
192//! // 3: Option::None detected
193//! //
194//! // With the disclose feature disabled the code will emit:
195//! // Top level failure
196//! ```
197//!
198//! ### Using closures
199//!
200//! The `convert` and `examine` macros may used with closures provided they are delimited with
201//! curly braces. The example shown here encloses the vector get, as above, in a closure.
202//!
203//! ```
204//! use nuhound::{Report, ResultExtension, OptionExtension, examine, convert};
205//!
206//! fn my_closure_test() -> Report<()> {
207//!     let vector = vec![0,1,2,3];
208//!     let index = 4;
209//!     // Notice that the closure is delimited with curly braces
210//!     let _ = convert!({|| 
211//!         vector.get(index)
212//!     }(), "Index out of range")?;
213//!     Ok(())
214//! }
215//!
216//! fn layer2() -> Report<()> {
217//!     let _result = examine!(my_closure_test(), "Layer 2 failure")?;
218//!     Ok(())
219//! }
220//!
221//! fn layer1() -> Report<()> {
222//!     let _result = examine!(layer2(), "Top level failure")?;
223//!     Ok(())
224//! }
225//!
226//! match layer1() {
227//!     Ok(_) => unreachable!(),
228//!     Err(e) => {
229//!         #[cfg(feature = "disclose")]
230//!         eprintln!("{}", e.trace());
231//!         #[cfg(not(feature = "disclose"))]
232//!         eprintln!("{}", e);
233//!     },
234//! }
235//! // With the disclose feature enabled the code will emit:
236//! // 0: src/main.rs:20:23: Top level failure
237//! // 1: src/main.rs:15:23: Layer 2 failure
238//! // 2: src/main.rs:8:17: Index out of range
239//! // 3: Option::None detected
240//! //
241//! // With the disclose feature disabled the code will emit:
242//! // Top level failure
243//! ```
244//!
245
246#![allow(unused)]
247use std::error::Error;
248use std::fmt;
249pub use proc_nuhound::{examine, convert, custom};
250use std::any::Any;
251
252/// The Report typedef is used to simplify [`Result`] enum usage when using the nuhound crate
253///
254/// # Example
255/// ```
256/// use nuhound::{Report, here, OptionExtension};
257///
258/// fn generate_error() -> Report<()> {
259///     let value = None;
260///     let message = "This is a test error messaage";
261///     value.report(|e| here!(e, "{}", message))?;
262///     Ok(())
263/// }
264///
265/// let result = generate_error();
266///
267/// assert!(result.is_err());
268/// println!("{:?}", result);
269/// ```
270pub type Report<T> = Result<T, Nuhound>;
271
272//  here macro
273/// Macro to prepare a Nuhound type error that can be handled by the calling context either by using
274/// the '?' operator or by simply returning it as a Result::Err directly.
275///
276/// The macro creates an error message that can optionally contain the name of the source file and
277/// location of the error. This behaviour is enabled by compiling the code with the 'disclose'
278/// feature enabled.
279///
280/// This macro is particularly useful when using the `report` trait that can be found in
281/// nuhound::OptionExtension or nuhound::ResultExtension..
282///
283/// # Examples
284/// The following example shows how the `here` macro is used to report an error but still retain
285/// the underlying error or errors that can be displayed using the `trace` method.
286/// ```
287/// use nuhound::{Report, here, ResultExtension};
288///
289/// fn generate_error() -> Report<u32> {
290///     let text = "NaN";
291///     let value = text.parse::<u32>().report(|e| here!(e, "Oh dear - '{}' could not be \
292///     converted to an integer", text))?;
293///     Ok(value)
294/// }
295///
296/// let result = generate_error();
297///
298/// match result {
299///     Ok(_) => unreachable!(),
300///     Err(e) => {
301///         println!("Display the error:\n{e}\n");
302///         println!("Or trace the error:\n{}\n", e.trace());
303///     }
304/// }
305/// // This will emit:
306/// // Display the error:
307/// // Oh dear - 'NaN' could not be converted to an integer
308/// //
309/// // Or trace the error:
310/// // 0: Oh dear - 'NaN' could not be converted to an integer
311/// // 1: invalid digit found in string
312/// //
313/// // This will also show the name of the file causing the error
314/// // and the line and column number if the code is compiled with
315/// // the disclose feature enabled.
316///```
317///
318/// This example shows how the `here` macro in conjunction with the `Root` token can be used to
319/// report a custom error omiting the underlying cause. Notice the trace method no longer emits
320/// 'invalid digit found in string'.
321///```
322/// use nuhound::{Report, here, ResultExtension};
323/// 
324/// fn generate_error() -> Report<u32> {
325///     let text = "NaN";
326///     let value = text.parse::<u32>().report(|_| here!(Root, "Oh dear - '{}' could not be \
327///     converted to an integer", text))?;
328///     Ok(value)
329/// }
330/// 
331/// let result = generate_error();
332/// 
333/// match result {
334///     Ok(_) => unreachable!(),
335///     Err(e) => {
336///         println!("Display the error:\n{e}\n");
337///         println!("Or trace the error:\n{}\n", e.trace());
338///     }
339/// }
340/// // This will emit:
341/// // Display the error:
342/// // Oh dear - 'NaN' could not be converted to an integer
343/// //
344/// // Or trace the error:
345/// // 0: Oh dear - 'NaN' could not be converted to an integer
346/// //
347/// // This will also show the name of the file causing the error
348/// // and the line and column number if the code is compiled with
349/// // the disclose feature enabled.
350/// ```
351///
352/// This example shows the `here` macro being used to convert the underlying error message to a
353/// Nuhound error. This enables the underlying file and location to be displayed when the code is
354/// compiled with the disclose feature enabled.
355/// ```
356/// use nuhound::{Report, here, ResultExtension};
357/// 
358/// fn generate_error() -> Report<u32> {
359///     let text = "NaN";
360///     let value = text.parse::<u32>().report(|e| here!(e))?;
361///     Ok(value)
362/// }
363/// 
364/// let result = generate_error();
365/// 
366/// match result {
367///     Ok(_) => unreachable!(),
368///     Err(e) => {
369///         println!("Display the error:\n{e}\n");
370///         println!("Or trace the error:\n{}\n", e.trace());
371///     }
372/// }
373/// // This will emit:
374/// // Display the error:
375/// // invalid digit found in string
376///
377/// // Or trace the error:
378/// // 0: invalid digit found in string
379/// ```
380///
381/// This example shows the `here` macro being used standalone. Note that it should be used with the
382/// 'Root' token because there are no other associated errors.
383/// ```
384/// use nuhound::{Report, here};
385/// 
386/// fn generate_error() -> Report<u32> {
387///     let value = 23_u32;
388///     if value == 23 {
389///         return Err(here!(Root, "value 23 not allowed"));
390///     }
391///     Ok(42)
392/// }
393/// 
394/// let result = generate_error();
395/// 
396/// match result {
397///     Ok(_) => unreachable!(),
398///     Err(e) => println!("{e}"),
399/// }
400/// // This will emit:
401/// // value 23 not allowed
402/// ```
403#[macro_export]
404macro_rules! here {
405    () => {
406        $crate::here!(Root)
407    };
408    ( Root ) => {
409        $crate::here!(Root, "unspecified error")
410    };
411    ( Root, $($inform:expr),+ ) => {{
412        let inform = format!( $($inform),+ );
413        #[cfg(feature="disclose")]
414        let inform = format!("{}:{}:{}: {}", file!(), line!(), column!(), inform);
415        $crate::Nuhound::new(inform)
416    }};
417    ( $caused_by:expr ) => {{
418        let cause: &dyn std::error::Error = &$caused_by;
419        match cause.source() {
420            Some(source) => $crate::here!(source, "{}", $caused_by),
421            None => $crate::here!(Root , "{}", $caused_by),
422        }
423    }};
424    ( $caused_by:expr, $($inform:expr),+ ) => {{
425        let mut cause: &dyn std::error::Error = &$caused_by;
426        let mut causes = vec![$crate::Nuhound::new(cause)];
427        while cause.source().is_some() {
428            cause = cause.source().unwrap();
429            causes.push($crate::Nuhound::new(cause));
430        }
431
432        let mut current = causes.pop();
433        let mut chain = current.unwrap();
434        current = causes.pop();
435        while current.is_some() {
436            chain = current.unwrap().caused_by(chain);
437            current = causes.pop();
438        }
439
440        $crate::here!(Root, $($inform),+).caused_by(chain)
441    }};
442}
443
444/// The structure holds the current error message as well as previous errors in a source chain that
445/// is represented as a *cons list*. Enhanced debugging can be enabled by compiling the code with
446/// the disclose feature enabled. This feature is available when Nuhound errors are generated using
447/// the following macros: `here!`, `convert!`, `examine!` and `custom!`. Enhanced debugging
448/// generates an error trace containing the source file name, line number and column number back to
449/// the originating code.
450///
451/// # Example
452///
453/// ```
454/// use std::fs::File;
455/// 
456/// use nuhound::{
457///     Report,
458///     here,
459///     ResultExtension,
460/// };
461/// 
462/// // Attempt to open a file that doesn't exist
463/// fn level2() -> Report<()> {
464///     // I assume there is no file in the current directory called this!
465///     let filename = "xuhgd56qhsl";
466///     let _file = File::open(filename).report(|e| here!(e, "Failed to open file '{}'", filename))?;
467///     Ok(())
468/// }
469/// 
470/// fn level1() -> Report<()> {
471///     level2().report(|e| here!(e, "Well that's another fine mess"))?;
472///     Ok(())
473/// }
474/// 
475/// fn level0() -> Report<()> {
476///     level1().report(|e| here!(e, "My user interface didn't work"))?;
477///     Ok(())
478/// }
479/// 
480/// fn run() -> Report<()> {
481///     level0().report(|e| here!(e, "Better tell the end user"))?;
482///     Ok(())
483/// }
484/// 
485/// // Using the trace method in conjunction with the disclose feature helps
486/// // the debugging process by showing exactly where the error occured
487/// match run() {
488///     Err(e) => println!("{}", e.trace()),
489///     Ok(_) => unreachable!(),
490/// };
491/// ```
492#[derive(Debug, Clone, PartialEq, Eq)]
493pub struct Nuhound {
494    source: Option<Box<Nuhound>>,
495    message: String,
496}
497
498impl Error for Nuhound {
499    /// Returns the source of the current error or `None` if no source information is available.
500    fn source(&self) -> Option<&(dyn Error + 'static)> {
501        match &self.source {
502            Some(source) => Some(source.as_ref()),
503            None => None,
504        }
505    }
506}
507
508impl fmt::Display for Nuhound {
509    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
510        write!(f, "{}", self.message)
511    }
512}
513
514impl From<&str> for Nuhound {
515    fn from(value: &str) -> Nuhound {
516        Nuhound::new(value.to_string())
517    }
518}
519
520impl Nuhound {
521    /// Create a Nuhound error.
522    ///
523    /// # Example
524    ///
525    /// ```
526    /// use nuhound::Nuhound;
527    ///
528    /// let e = Nuhound::new("My custom error");
529    /// ```
530    pub fn new(inform: impl fmt::Display) -> Self {
531        Self {
532            source: None,
533            message: inform.to_string(),
534        }
535    }
536
537    /// Create a Nuhound error chain by appending and consolidating an existing error chain.
538    /// Each element in the chain is converted into a Nuhound type.
539    ///
540    /// # Example
541    ///
542    /// ```
543    /// use nuhound::{Nuhound, is_nuhound};
544    /// use std::num::ParseIntError;
545    ///
546    /// fn generate_error() -> Result<u32, ParseIntError> {
547    ///     let text = "NaN";
548    ///     // This will fail because 'NaN' is not an integer
549    ///     let value = text.parse::<u32>()?;
550    ///     Ok(value)
551    /// }
552    ///
553    /// match generate_error() {
554    ///     Ok(_) => unreachable!(),
555    ///     Err(e) => {
556    ///         assert!(!is_nuhound(&e)); // This isn't a nuhound error
557    ///         // Convert the underlying error 'e' to a Nuhound by linking
558    ///         let my_error = Nuhound::link("Parse Integer failed", e);
559    ///         assert!(is_nuhound(&my_error)); // This is a nuhound error
560    ///         assert_eq!(my_error.trace(), " 0: Parse Integer failed\n 1: invalid digit found in string");
561    ///     },
562    /// }
563    /// ```
564    pub fn link(inform: impl fmt::Display, caused_by: impl Error) -> Self {
565        // Take the whole chain converting each to Nuhound along the way
566        // We assume that the chain may contain non-Nuhound errors
567        let mut cause: &dyn Error = &caused_by;
568        let mut causes = vec![Nuhound::new(cause)];
569        while cause.source().is_some() {
570            cause = cause.source().unwrap();
571            causes.push(Nuhound::new(cause));
572        }
573        let mut current = causes.pop();
574        let mut chain = current.unwrap();
575        current = causes.pop();
576        while current.is_some() {
577            chain = current.unwrap().caused_by(chain);
578            current = causes.pop();
579        }
580
581        // Finally add the top level message 'inform' to the chain
582        Nuhound::new(inform).caused_by(chain)
583    }
584
585    /// Add a cause to an existing Nuhound error.
586    ///
587    /// ```
588    /// use nuhound::{Nuhound, OptionExtension};
589    ///
590    /// let error_source = vec![1, 2, 3, 4].get(4).easy().unwrap_err();
591    /// let my_error = Nuhound::new("Out of bounds").caused_by(error_source);
592    /// println!("{}", my_error.trace());
593    /// // emits:
594    /// //  0: Out of bounds
595    /// //  1: Option::None detected
596    /// ```
597    pub fn caused_by(mut self, source: Nuhound) -> Self {
598        self.source = Some(Box::new(source));
599        self
600    }
601 
602    /// Create a list of errors starting at the most recent error and working backwards towards the
603    /// the error source.
604    ///
605    /// ```
606    /// use nuhound::{Nuhound, OptionExtension};
607    ///
608    /// let error_source = vec![1, 2, 3, 4].get(4).easy().unwrap_err();
609    /// let my_error = Nuhound::new("Out of bounds").caused_by(error_source);
610    /// println!("{}", my_error.trace());
611    /// // emits:
612    /// //  0: Out of bounds
613    /// //  1: Option::None detected
614    /// ```
615    pub fn trace(&self) -> String {
616        let mut trace_list = vec![format!(" 0: {}", self)];
617        let mut n = 1;
618        let mut item = self.source.as_ref();
619        while item.is_some() {
620            let this = item.unwrap();
621            trace_list.push(format!("{:2}: {}", n, this));
622            item = this.source.as_ref();
623            n += 1;
624        }
625        trace_list.join("\n")
626    }
627}
628
629/// Provides `Nuhound` trait support to `std::result::Result`. Remember to `use` this if you're
630/// intending to use the `report()` and/or `easy()` methods with values of type `Result<T, E>` or
631/// functions that return `Result<T, E>`.
632pub trait ResultExtension<T, E> {
633    /// Calls op lazily if the result is Err, otherwise returns the Ok value of self.
634    ///
635    /// This function can be used for control flow based on result values and is similar to the
636    /// map_err function in the standard library. This function returns only Nuhound type errors and
637    /// is designed to work well with the `here` macro.
638    ///
639    /// # Example:
640    ///
641    /// ```
642    /// use nuhound::{Report, here, ResultExtension};
643    /// 
644    /// fn generate_error() -> Report<u32> {
645    ///     let text = "NaN";
646    ///     let value = text.parse::<u32>().report(|e| here!(e))?;
647    ///     Ok(value)
648    /// }
649    /// 
650    /// let result = generate_error();
651    /// 
652    /// match result {
653    ///     Ok(_) => unreachable!(),
654    ///     Err(e) => println!("Display the error:\n{e}\n"),
655    /// }
656    /// // This will emit:
657    /// // Display the error:
658    /// // invalid digit found in string
659    /// ```
660    fn report<O: FnOnce(E) -> Nuhound>(self, op: O) -> Result<T, Nuhound>;
661
662    /// Lazily converts any error into a nuhound error, otherwise returns the Ok value of self.
663    ///
664    /// # Example:
665    ///
666    /// ```
667    /// use nuhound::{Report, ResultExtension};
668    /// 
669    /// fn generate_error() -> Report<u32> {
670    ///     let text = "NaN";
671    ///     let value = text.parse::<u32>().easy()?;
672    ///     Ok(value)
673    /// }
674    /// 
675    /// let result = generate_error();
676    /// 
677    /// match result {
678    ///     Ok(_) => unreachable!(),
679    ///     Err(e) => println!("{e}"),
680    /// }
681    /// // This will emit:
682    /// // invalid digit found in string
683    /// ```
684    fn easy(self) -> Result<T, Nuhound>;
685}
686
687impl<T, E: Error> ResultExtension<T, E> for Result<T, E> {
688    fn report<O: FnOnce(E) -> Nuhound>(self, op: O) -> Result<T, Nuhound> {
689        match self {
690            Ok(val) => Ok(val),
691            Err(e) => Err(op(e)),
692        }
693    }
694
695    fn easy(self) -> Result<T, Nuhound> {
696        match self {
697            Ok(val) => Ok(val),
698            Err(e) => {
699                match e.source() {
700                    Some(source) => {
701                        let mut cause: &dyn Error = &source;
702                        let mut causes = vec![Nuhound::new(cause)];
703                        while cause.source().is_some() {
704                            cause = cause.source().unwrap();
705                            causes.push(Nuhound::new(cause));
706                        }
707
708                        let mut current = causes.pop();
709                        let mut chain = current.unwrap();
710                        current = causes.pop();
711                        while current.is_some() {
712                            chain = current.unwrap().caused_by(chain);
713                            current = causes.pop();
714                        }
715                        Err(Nuhound::new(e).caused_by(chain))
716                    },
717                    None => Err(Nuhound::new(e)),
718                }
719            },
720        }
721    }
722}
723
724/// Provides `Nuhound` trait support to `std::option::Option`. Remember to `use` this if you're
725/// intending to use the `report()` and/or `easy()` methods with values of type `Option<T>` or functions that
726/// return `Option<T>`.
727pub trait OptionExtension<T> {
728    /// Transforms the `Option<T>` into a [`Result<T, Nuhound>`]
729    ///
730    /// This function has some simarlarity to ok_or_else in the standard library except that this
731    /// returns a Nuhound type error and that a Nuhound error is passed as a paramter to op. It is
732    /// designed to work well with the `here` macro.
733    ///
734    /// # Example
735    ///
736    /// ```
737    /// use nuhound::{Report, here, OptionExtension};
738    ///
739    /// fn oob() -> Report<u32> {
740    ///    let list: Vec<u32> = vec![1, 2, 3, 4,];
741    ///    let bad_val = *list.get(4).report(|e| here!(e, "Index out of bounds"))?;
742    ///    Ok(bad_val)
743    /// }
744    /// let bad = oob().unwrap_err();
745    /// println!("{}", bad.trace());
746    /// ```
747    fn report<O: FnOnce(Nuhound) -> Nuhound>(self, op: O) -> Result<T, Nuhound>;
748
749    /// Transforms the `Option<T>` into a [`Result<T, Nuhound>`].
750    ///
751    /// This is a simple method of transforming an Option into a Result
752    ///
753    /// # Example
754    ///
755    /// ```
756    /// use nuhound::{Report, OptionExtension};
757    ///
758    /// fn oob() -> Report<u32> {
759    ///    let list: Vec<u32> = vec![1, 2, 3, 4,];
760    ///    let bad_val = *list.get(4).easy()?;
761    ///    Ok(bad_val)
762    /// }
763    /// let bad = oob().unwrap_err();
764    /// println!("{bad}");
765    /// ```
766    fn easy(self) -> Result<T, Nuhound>;
767}
768
769impl<T> OptionExtension<T> for Option<T> {
770    fn report<O: FnOnce(Nuhound) -> Nuhound>(self, op: O) -> Result<T, Nuhound> {
771        match self {
772            Some(val) => Ok(val),
773            None => Err(op(Nuhound::new("Option::None detected"))),
774        }
775    }
776
777    fn easy(self) -> Result<T, Nuhound> {
778        match self {
779            Some(val) => Ok(val),
780            None => Err(Nuhound::new("Option::None detected")),
781        }
782    }
783}
784
785/// Determines whether the value is of type `Nuhound`
786///
787/// # Example
788///
789/// ```
790/// use nuhound::{Report, here, ResultExtension, is_nuhound};
791///
792/// fn generate_error() -> Report<u32> {
793///     let text = "NaN";
794///     let value = text.parse::<u32>().report(|e| here!(e, "Oh dear - '{}' could not be \
795///     converted to an integer", text))?;
796///     Ok(value)
797/// }
798///
799/// let result = generate_error();
800///
801/// match result {
802///     Ok(_) => unreachable!(),
803///     Err(e) => {
804///         println!("This is nuhound: {}", is_nuhound(&e));
805///         // This will print 'true' to confirm the error is of type nuhound
806///         #[cfg(feature = "disclose")]
807///         eprintln!("{}", e.trace());
808///         #[cfg(not(feature = "disclose"))]
809///         eprintln!("{}", e);
810///     },
811/// }
812/// ```
813pub fn is_nuhound(val: &dyn Any) -> bool {
814    val.is::<Nuhound>()
815}
816
817#[cfg(test)]
818mod tests {
819    use super::*;
820    use regex::Regex;
821
822    #[test]
823    fn test_01() -> Report<()> {
824        fn good_value() -> Report<u32> {
825            let value = "999".parse::<u32>()
826                .report(|_| here!())?;
827            Ok(value)
828        }
829        fn bad_value() -> Report<u32> {
830            let value = "NaN".parse::<u32>()
831                .report(|_| here!())?;
832            Ok(value)
833        }
834        assert_eq!(good_value()?, 999);
835        let value = bad_value().unwrap_err().to_string(); 
836        if cfg!(feature = "disclose") {
837            let re = Regex::new(r"^src[\\/]lib\.rs:\d+:\d+: unspecified error$").unwrap();
838            assert!(re.is_match(&value));
839        } else {
840            assert_eq!(value, "unspecified error");
841        }
842        Ok(())
843    }
844
845    #[test]
846    fn test_02() {
847        fn bad_value() -> Report<u32> {
848            let value = "NaN".parse::<u32>()
849                .report(|_| here!(Root))?;
850            Ok(value)
851        }
852        let value = bad_value().unwrap_err().to_string(); 
853        if cfg!(feature = "disclose") {
854            let re = Regex::new(r"^src[\\/]lib\.rs:\d+:\d+: unspecified error$").unwrap();
855            assert!(re.is_match(&value));
856        } else {
857            assert_eq!(value, "unspecified error");
858        }
859    }
860
861    #[test]
862    fn test_03() {
863        fn bad_value() -> Report<u32> {
864            let text = "error";
865            let value = "NaN".parse::<u32>()
866                .report(|_| here!(Root, "this is an {text}"))?;
867            Ok(value)
868        }
869        let value = bad_value().unwrap_err().to_string(); 
870        if cfg!(feature = "disclose") {
871            let re = Regex::new(r"^src[\\/]lib\.rs:\d+:\d+: this is an error$").unwrap();
872            assert!(re.is_match(&value));
873        } else {
874            assert_eq!(value, "this is an error");
875        }
876    }
877
878    #[test]
879    fn test_04() {
880        fn bad_value() -> Report<u32> {
881            let value = "NaN".parse::<u32>()
882                .report(|e| here!(e))?;
883            Ok(value)
884        }
885        let value = bad_value().unwrap_err().to_string(); 
886        if cfg!(feature = "disclose") {
887            let re = Regex::new(r"^src[\\/]lib\.rs:\d+:\d+: invalid digit found in string$").unwrap();
888            assert!(re.is_match(&value));
889        } else {
890            assert_eq!(value, "invalid digit found in string");
891        }
892    }
893
894    #[test]
895    fn test_05() {
896        fn bad_value() -> Report<u32> {
897            let value = "NaN".parse::<u32>()
898                .report(|e| here!(e, "cannot convert string to a number"))?;
899            Ok(value)
900        }
901        let value = bad_value().unwrap_err().to_string(); 
902        if cfg!(feature = "disclose") {
903            let re = Regex::new(r"^src[\\/]lib\.rs:\d+:\d+: cannot convert string to a number$").unwrap();
904            assert!(re.is_match(&value));
905        } else {
906            assert_eq!(value, "cannot convert string to a number");
907        }
908    }
909
910    #[test]
911    fn test_06() {
912        fn bad_value() -> Report<u32> {
913            let value = "NaN".parse::<u32>()
914                .report(|e| here!(e, "cannot convert string to a number"))?;
915            Ok(value)
916        }
917        let value = bad_value().unwrap_err().trace(); 
918        let values: Vec<&str> = value.split('\n').collect();
919        if cfg!(feature = "disclose") {
920            let re0 = Regex::new(r"^ 0: src[\\/]lib\.rs:\d+:\d+: cannot convert string to a number$").unwrap();
921            let re1 = Regex::new(r"^ 1: invalid digit found in string$").unwrap();
922            assert!(re0.is_match(&values[0]));
923            assert!(re1.is_match(&values[1]));
924        } else {
925            assert_eq!(values[0], " 0: cannot convert string to a number");
926            assert_eq!(values[1], " 1: invalid digit found in string");
927        }
928    }
929
930    #[test]
931    fn test_07() {
932        fn oob() -> Report<u32> {
933            let list: Vec<u32> = vec![1, 2, 3, 4,];
934            let good_val = *list.get(3).report(|e| here!(e, "Index out of bounds"))?;
935            assert_eq!(good_val, 4);
936            let bad_val = *list.get(4).report(|e| here!(e, "Index out of bounds"))?;
937            Ok(bad_val)
938        }
939        let bad = oob().unwrap_err();
940        let source = bad.source().unwrap();
941        if cfg!(feature = "disclose") {
942            let re = Regex::new(r"^src[\\/]lib\.rs:\d+:\d+: Index out of bounds$").unwrap();
943            assert!(re.is_match(&bad.to_string()));
944        } else {
945            assert_eq!(bad.to_string(), "Index out of bounds");
946        }
947        assert_eq!(source.to_string(), "Option::None detected");
948    }
949
950    #[test]
951    fn test_08() {
952        fn bad_value() -> Report<u32> {
953            let value = "NaN".parse::<u32>()
954                .report(|e| here!(e, "cannot convert string to a number"))?;
955            Ok(value)
956        }
957        fn easy_test() -> Report<u32> {
958            let value = bad_value().easy()?;
959            Ok(value)
960        }
961        let value = easy_test().unwrap_err().trace(); 
962        let values: Vec<&str> = value.split('\n').collect();
963        if cfg!(feature = "disclose") {
964            let re0 = Regex::new(r"^ 0: src[\\/]lib\.rs:\d+:\d+: cannot convert string to a number$").unwrap();
965            let re1 = Regex::new(r"^ 1: invalid digit found in string$").unwrap();
966            assert!(re0.is_match(&values[0]));
967            assert!(re1.is_match(&values[1]));
968        } else {
969            assert_eq!(values[0], " 0: cannot convert string to a number");
970            assert_eq!(values[1], " 1: invalid digit found in string");
971        }
972    }
973
974    #[test]
975    fn test_09() {
976        fn bad_value() -> Report<u32> {
977            let value = "NaN".parse::<u32>()
978                .easy()?;
979            Ok(value)
980        }
981        let value = bad_value().unwrap_err().to_string(); 
982        assert_eq!(value, "invalid digit found in string");
983    }
984
985    #[test]
986    fn test_10() {
987        fn oob() -> Report<u32> {
988            let list: Vec<u32> = vec![1, 2, 3, 4,];
989            let good_val = *list.get(3).easy()?;
990            assert_eq!(good_val, 4);
991            let bad_val = *list.get(4).easy()?;
992            Ok(bad_val)
993        }
994        let value = oob().unwrap_err().to_string(); 
995        assert_eq!(value, "Option::None detected");
996    }
997}