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}