Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_core/src/error.rs

26.7 KiB, 66 runs

created by r1870400018:76, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! # Error Handling in Hematite
2//!
3//! Hematite's error handling system balances performance with robust error management through
4//! a layered approach to error propagation and contextualisation.
5//!
6//! ## Core Types
7//!
8//! - `Outcome<V>`: The main result type, an alias for `strd::Result::Result<V, Error<error::ErrTag>>`
9//! - `Error<T>`: An error type that can represent local errors, upstream errors, or error collections
10//! - `ErrMsg`: Contains an error message and associated tags
11//! - `ErrTag`: An enumeration of standard error categories
12//!
13//! ## Error Handling Macros
14//!
15//! Hematite provides three error handling macros with different capabilities:
16//!
17//! ### `ok!` - Basic Error Propagation
18//! ```rust
19//! // Equivalent to the ? operator but with prefix syntax.
20//! let file = ok!(File::create("data.txt"));
21//! ```
22//! - Lightweight error propagation using standard `From` trait
23//! - A prefix alias for the `?` operator suffix
24//! - Ideal for performance-critical paths
25//!
26//! ### `res!` - Contextual Error Propagation
27//! ```rust
28//! // Adds context and tags to errors.
29//! let file = res!(File::create("data.txt"), IO, File);
30//! ```
31//! - Short for "result"
32//! - Adds file/line context and error tags
33//! - Thread-safe error sharing via `Arc`
34//! - Small overhead from context capture
35//! - Suitable for general application code
36//!
37//! ### `catch!` - Panic-Catching Error Propagation
38//! ```rust
39//! // Converts both errors and panics into Outcome
40//! let value = catch!(compute_result(), Numeric, Overflow);
41//! ```
42//! - Catches unwinding panics and converts them to errors
43//! - Adds context and tags like `ok!`
44//! - Higher overhead due to unwinding tables
45//! - Most suitable for top-level error boundaries
46//!
47//! ## Error Tags
48//!
49//! Error tags provide multi-dimensional error classification:
50//!
51//! ```rust
52//! // Multiple tags can be attached
53//! let result = res!(operation(), IO, Network, Timeout);
54//!
55//! // Tags can be matched efficiently
56//! if err.tags().contains(&ErrTag::Timeout) {
57//! // Handle timeout case
58//! }
59//! ```
60//!
61//! ## Performance Considerations
62//!
63//! - `ok!`: Minimal overhead, uses standard error conversion
64//! - `res!`: Small overhead from `Arc` and context capture
65//! - `catch!`: Larger overhead from unwinding support
66//! - Requires unwinding tables in binary
67//! - Extra stack frame management
68//! - Cannot be nested due to closure limitations
69//!
70//! ## Design Philosophy
71//!
72//! Hematite's error handling aims to:
73//! - Provide flexible error handling tools for different needs
74//! - Make error handling strategies explicit at call sites
75//! - Allow users to choose appropriate performance/feature tradeoffs
76//! - Maintain rich context for debugging
77//! - Convert panics to manageable errors where beneficial
78//!
79//! The system balances comprehensive error handling against performance, allowing developers to
80//! choose the appropriate level of error management for their specific needs.
81//!
82//! The default macro used in Hematite is `res!` for enhanced error reporting. Foundational code
83//! should migrate to `ok!` when improved performance is needed, while `catch!` should be used for
84//! top-level application code.
85//!
86use crate::GenTag;
87
88use crate::term::Term;
89
90use std::{
91 fmt,
92 io,
93 num,
94 string,
95 sync::Arc,
96};
97use std::convert::From;
98
99
100#[allow(non_camel_case_types)]
101#[derive(Clone, Debug, Eq, PartialEq)]
102pub enum ErrTag {
103 Async,
104 Borrow,
105 Bug, // The fault is with the developer, not the user.
106 Bytes, // TODO deprecate
107 Binary,
108 Channel,
109 Checksum,
110 Configuration,
111 Conflict,
112 Conversion,
113 Counter,
114 Create,
115 Data,
116 Daticle,
117 Decode,
118 Decrypt,
119 Divisibility,
120 Duplicate,
121 Encode,
122 Encrypt,
123 Excessive,
124 Exists,
125 Fatal,
126 File,
127 Format,
128 Identifier,
129 Index,
130 Init,
131 Input,
132 Integer,
133 Interrupted,
134 Invalid,
135 IO,
136 Key,
137 LimitReached,
138 Lock,
139 Mismatch,
140 Missing,
141 Name,
142 Network,
143 NoImpl,
144 NotFound,
145 Numeric,
146 Order,
147 Output,
148 Overflow,
149 Panic,
150 Path,
151 Permanent, // A failure that retrying will not cure; the operation must not be attempted again.
152 Poisoned,
153 Range,
154 Read,
155 Sealed, // A key-protected resource is locked; it must be unsealed first.
156 Security,
157 Seek,
158 Size,
159 Slice,
160 String,
161 Suggestion,
162 System,
163 Test,
164 Timeout,
165 Thread,
166 TooBig,
167 TooSmall,
168 Wire,
169 Write,
170 Unauthorised,
171 Unconfirmed, // An effect that may well have happened, whose confirmation did not come: not a failure.
172 Underflow,
173 Unexpected,
174 Unimplemented,
175 Unknown,
176 Unreachable,
177 Upstream,
178 UTF8,
179 Value,
180 Version,
181 ZeroDenominator,
182}
183
184#[derive(Clone, Debug, Default, Eq, PartialEq)]
185pub struct ErrMsg<T: GenTag> {
186 pub msg: String,
187 pub tags: &'static [T],
188}
189
190fn without_location(msg: &str) -> &str {
191 match msg.split_once(": ") {
192 Some((head, tail)) if is_location(head) => tail,
193 // The whole message may be a location and nothing else, which is what a frame that merely
194 // wraps another error looks like. It has nothing to say, so it says nothing.
195 _ => if is_location(msg) { "" } else { msg },
196 }
197}
198
199fn is_location(s: &str) -> bool {
200 match s.rsplit_once(':') {
201 Some((file, line)) => {
202 !file.is_empty()
203 && !line.is_empty()
204 && line.chars().all(|c| c.is_ascii_digit())
205 },
206 None => false,
207 }
208}
209
210fn push_words(out: &mut Vec<String>, msg: &str) {
211 let msg = msg.trim();
212 if !msg.is_empty() {
213 out.push(msg.to_string());
214 }
215}
216
217impl<T: GenTag> fmt::Display for ErrMsg<T> {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 write!(f, "{:?}", self)
220 }
221}
222
223#[derive(Clone)]
224pub enum Error<T: GenTag> {
225 Local(ErrMsg<T>),
226 Other(ErrMsg<T>),
227 Upstream(Arc<dyn std::error::Error + Send + Sync>, ErrMsg<T>),
228 Collection(Vec<Box<Self>>),
229}
230
231impl<T: GenTag> Error<T> where Error<T>: std::error::Error {
232
233 /// Every tag the error carries, its own and those of each error it wraps, outermost first. A
234 /// `res!` frame adds no tags of its own, so reading the outer frame alone loses the tags the fault
235 /// was raised with.
236 pub fn tags(&self) -> Vec<T> {
237 let mut out = Vec::new();
238 self.gather_tags(&mut out);
239 out
240 }
241
242 fn gather_tags(&self, out: &mut Vec<T>) {
243 match self {
244 Error::Local(ErrMsg { tags: t, ..}) |
245 Error::Other(ErrMsg { tags: t, ..}) => out.extend(t.iter().cloned()),
246 Error::Upstream(arc_e, ErrMsg { tags: t, ..}) => {
247 out.extend(t.iter().cloned());
248 // A foreign cause may itself wrap one of ours, so the walk goes on through it.
249 let mut cause: Option<&(dyn std::error::Error + 'static)> = Some(arc_e.as_ref());
250 while let Some(c) = cause {
251 if let Some(e) = c.downcast_ref::<Error<T>>() {
252 e.gather_tags(out);
253 break;
254 }
255 cause = c.source();
256 }
257 },
258 Error::Collection(boxerrs) => {
259 for e in boxerrs {
260 e.gather_tags(out);
261 }
262 },
263 }
264 }
265
266 pub fn msgs(&self) -> Vec<String> {
267 let mut out = Vec::new();
268 self.gather(&mut out);
269 out
270 }
271
272 fn gather(&self, out: &mut Vec<String>) {
273 match self {
274 Error::Local(ErrMsg { msg: m, .. }) |
275 Error::Other(ErrMsg { msg: m, .. }) => push_words(out, without_location(m)),
276 Error::Upstream(arc_e, ErrMsg { msg: m, .. }) => {
277 push_words(out, without_location(m));
278 match arc_e.downcast_ref::<Error<T>>() {
279 // One of ours, so its words are reachable and are gathered in turn.
280 Some(e) => e.gather(out),
281 // Somebody else's, so all we have is what it says of itself. `Display` is the
282 // right form here: a foreign error has no ANSI colour of ours in it.
283 None => push_words(out, &fmt!("{}", arc_e)),
284 }
285 },
286 Error::Collection(boxerrs) => {
287 for boxerr in boxerrs {
288 boxerr.gather(out);
289 }
290 },
291 }
292 }
293
294 pub fn plain(&self) -> String {
295 let msgs = self.msgs();
296 if msgs.is_empty() {
297 // An error with no words at all still has its tags, and they beat saying nothing.
298 let tags = Self::tags_display(self.tags());
299 if tags.is_empty() {
300 return "An error carrying neither a message nor a tag.".to_string();
301 }
302 return tags;
303 }
304 msgs.join(" ")
305 }
306
307 pub fn tags_display(tags: Vec<T>) -> String {
308 let mut result = String::new();
309 if tags.len() > 0 {
310 result.push('[');
311 let mut c = 0;
312 for tag in tags {
313 if c > 0 {
314 result.push(' ');
315 }
316 result.push_str(&tag.to_string());
317 c += 1;
318 }
319 result.push(']');
320 }
321 result
322 }
323
324 fn fmt_debug_local(
325 f: &mut fmt::Formatter<'_>,
326 m: &str,
327 t: &'static [T],
328 )
329 -> fmt::Result
330 {
331 write!(f, "LocalErr{{{}{}}}",
332 Self::tags_display(t.to_vec()),
333 if m.len() > 0 {
334 if t.len() > 0 {
335 fmt!(" \"{}\"", m)
336 } else {
337 fmt!("\"{}\"", m)
338 }
339 } else {
340 String::new()
341 },
342 )
343 }
344
345 fn fmt_debug_upstream_specific(
346 f: &mut fmt::Formatter<'_>,
347 e: &Self,
348 m: &str,
349 t: &'static [T],
350 )
351 -> fmt::Result
352 {
353 write!(f, "UpstreamErr{{{}{}}}\n{:?}",
354 Self::tags_display(t.to_vec()),
355 if m.len() > 0 {
356 if t.len() > 0 {
357 fmt!(" \"{}\"", m)
358 } else {
359 fmt!("\"{}\"", m)
360 }
361 } else {
362 String::new()
363 },
364 e,
365 )
366 }
367
368 fn fmt_debug_upstream_general(
369 f: &mut fmt::Formatter<'_>,
370 arc_e: &Arc<dyn std::error::Error + Send + Sync>,
371 m: &str,
372 t: &'static [T],
373 )
374 -> fmt::Result
375 {
376 write!(f, "UpstreamErr{{{}{}}}",
377 Error::tags_display(t.to_vec()),
378 if m.len() > 0 {
379 if t.len() > 0 {
380 fmt!(" \"{}\" \"{:?} {}\"",
381 m,
382 arc_e,
383 arc_e,
384 )
385 } else {
386 fmt!("\"{}\" \"{:?} {}\"",
387 m,
388 arc_e,
389 arc_e,
390 )
391 }
392 } else {
393 String::new()
394 },
395 )
396 }
397
398 fn fmt_display_local(
399 f: &mut fmt::Formatter<'_>,
400 m: &str,
401 t: &'static [T],
402 )
403 -> fmt::Result
404 {
405 write!(f,
406 //1 2 3 4 5 6 7 8 91011 12
407 "{}{}LocalErr{{{}{}{}{}{}{}{}{}{}}}{}",
408 Term::SET_BRIGHT_FORE_RED, // 1
409 Term::BOLD, // 2
410 Term::RESET, // 3
411 Term::FORE_MAGENTA, // 4
412 Error::tags_display(t.to_vec()), // 5
413 Term::RESET, // 6
414 Term::SET_BRIGHT_FORE_YELLOW, // 7
415 if m.len() > 0 {
416 if t.len() > 0 {
417 fmt!(" \"{}\"", m) // 8
418 } else {
419 fmt!("\"{}\"", m) // 8
420 }
421 } else {
422 String::new() // 8
423 },
424 Term::RESET, // 9
425 Term::SET_BRIGHT_FORE_RED, // 10
426 Term::BOLD, // 11
427 Term::RESET, // 12
428 )
429 }
430
431 fn fmt_display_upstream_specific(
432 f: &mut fmt::Formatter<'_>,
433 e: &Self,
434 m: &str,
435 t: &'static [T],
436 )
437 -> fmt::Result
438 {
439 write!(f,
440 //1 2 3 4 5 6 7 8 91011 12 13
441 "{}{}UpstreamErr{{{}{}{}{}{}{}{}{}{}}}{}\n{}",
442 Term::SET_BRIGHT_FORE_RED, // 1
443 Term::BOLD, // 2
444 Term::RESET, // 3
445 Term::FORE_MAGENTA, // 4
446 Error::tags_display(t.to_vec()), // 5
447 Term::RESET, // 6
448 Term::SET_BRIGHT_FORE_CYAN, // 7
449 if m.len() > 0 {
450 if t.len() > 0 {
451 fmt!(" \"{}\"", m) // 8
452 } else {
453 fmt!("\"{}\"", m) // 8
454 }
455 } else {
456 String::new() // 8
457 },
458 Term::RESET, // 9
459 Term::SET_BRIGHT_FORE_RED, // 10
460 Term::BOLD, // 11
461 Term::RESET, // 12
462 e, // 13
463 )
464 }
465
466 fn fmt_display_upstream_general(
467 f: &mut fmt::Formatter<'_>,
468 arc_e: &Arc<dyn std::error::Error + Send + Sync>,
469 m: &str,
470 t: &'static [T],
471 )
472 -> fmt::Result
473 {
474 write!(f,
475 //1 2 3 4 5 6 7 8 91011 12
476 "{}{}UpstreamErr{{{}{}{}{}{}{}{}{}{}}}{}",
477 Term::SET_BRIGHT_FORE_RED, // 1
478 Term::BOLD, // 2
479 Term::RESET, // 3
480 Term::FORE_MAGENTA, // 4
481 Error::tags_display(t.to_vec()), // 5
482 Term::RESET, // 6
483 Term::SET_BRIGHT_FORE_CYAN, // 7
484 if m.len() > 0 {
485 if t.len() > 0 {
486 fmt!(" \"{}\"{}{} {}\"{:?} {}\"{}", // 8
487 m,
488 Term::RESET,
489 Term::SET_BRIGHT_FORE_BLACK,
490 Term::BACK_YELLOW,
491 arc_e,
492 arc_e,
493 Term::RESET,
494 )
495 } else {
496 fmt!("\"{}\"{}{} {}\"{:?} {}\"{}", // 8
497 m,
498 Term::RESET,
499 Term::SET_BRIGHT_FORE_BLACK,
500 Term::BACK_YELLOW,
501 arc_e,
502 arc_e,
503 Term::RESET,
504 )
505 }
506 } else {
507 String::new() // 8
508 },
509 Term::RESET, // 9
510 Term::SET_BRIGHT_FORE_RED, // 10
511 Term::BOLD, // 11
512 Term::RESET, // 12
513 )
514 }
515}
516
517/// Plain, without ANSI terminal colour codes.
518impl<T: GenTag> fmt::Debug for Error<T> where Error<T>: std::error::Error {
519 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
520 match self {
521 Error::Local(ErrMsg {msg:m, tags: t}) |
522 Error::Other(ErrMsg {msg:m, tags: t}) => Self::fmt_debug_local(f, m, t),
523 Error::Upstream(arc_e, ErrMsg{msg: m, tags: t}) => match arc_e.downcast_ref::<Error<T>>() {
524 Some(e) => Self::fmt_debug_upstream_specific(f, e, m, t),
525 None => Self::fmt_debug_upstream_general(f, arc_e, m, t),
526 },
527 Error::Collection(boxerrs) => {
528 ok!(writeln!(f, "Collection of {} errors:", boxerrs.len()));
529 for (i, boxerr) in boxerrs.iter().enumerate() {
530 ok!(writeln!(f, "{:04}: {:?}", i, *boxerr));
531 }
532 Ok(())
533 },
534 }
535 }
536}
537
538/// For console use.
539impl<T: GenTag> fmt::Display for Error<T> where Error<T>: std::error::Error {
540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541 match self {
542 Error::Local(ErrMsg {tags: t, msg: m}) |
543 Error::Other(ErrMsg {tags: t, msg: m}) => Self::fmt_display_local(f, m, t),
544 Error::Upstream(arc_e, ErrMsg{tags: t, msg: m}) => match arc_e.downcast_ref::<Error<T>>() {
545 Some(e) => Self::fmt_display_upstream_specific(f, e, m, t),
546 None => Self::fmt_display_upstream_general(f, arc_e, m, t),
547 },
548 Error::Collection(boxerrs) => {
549 ok!(writeln!(f, "Collection of {} errors:", boxerrs.len()));
550 for (i, boxerr) in boxerrs.iter().enumerate() {
551 ok!(writeln!(f, "{:04}: {}", i, *boxerr));
552 }
553 Ok(())
554 },
555 }
556 }
557}
558
559// Automate conversions for standard library errors.
560//
561impl std::error::Error for Error<ErrTag> {
562 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
563 match self {
564 Error::Upstream(arc_e, _) => Some(arc_e.as_ref()),
565 _ => None,
566 }
567 }
568}
569
570impl From<fmt::Error> for Error<ErrTag> {
571 fn from(e: fmt::Error) -> Self {
572 Error::Upstream(Arc::new(e), ErrMsg {
573 tags: &[ErrTag::Format],
574 msg: String::new(),
575 })
576 }
577}
578
579impl From<io::Error> for Error<ErrTag> {
580 fn from(e: io::Error) -> Self {
581 Error::Upstream(Arc::new(e), ErrMsg {
582 tags: &[ErrTag::IO],
583 msg: String::new(),
584 })
585 }
586}
587
588impl From<string::FromUtf8Error> for Error<ErrTag> {
589 fn from(e: string::FromUtf8Error) -> Self {
590 Error::Upstream(Arc::new(e), ErrMsg {
591 tags: &[ErrTag::Decode, ErrTag::UTF8, ErrTag::String],
592 msg: String::new(),
593 })
594 }
595}
596
597impl From<std::str::Utf8Error> for Error<ErrTag> {
598 fn from(e: std::str::Utf8Error) -> Self {
599 Error::Upstream(Arc::new(e), ErrMsg {
600 tags: &[ErrTag::Decode, ErrTag::UTF8, ErrTag::String],
601 msg: String::new(),
602 })
603 }
604}
605
606impl From<num::ParseIntError> for Error<ErrTag> {
607 fn from(e: num::ParseIntError) -> Self {
608 Error::Upstream(Arc::new(e), ErrMsg {
609 tags: &[ErrTag::Decode, ErrTag::Integer, ErrTag::String],
610 msg: String::new(),
611 })
612 }
613}
614
615impl From<std::array::TryFromSliceError> for Error<ErrTag> {
616 fn from(e: std::array::TryFromSliceError) -> Self {
617 Error::Upstream(Arc::new(e), ErrMsg {
618 tags: &[ErrTag::Conversion, ErrTag::Slice],
619 msg: String::new(),
620 })
621 }
622}
623
624impl From<std::time::SystemTimeError> for Error<ErrTag> {
625 fn from(e: std::time::SystemTimeError) -> Self {
626 Error::Upstream(Arc::new(e), ErrMsg {
627 tags: &[ErrTag::Conversion, ErrTag::Slice],
628 msg: String::new(),
629 })
630 }
631}
632
633impl<T> From<std::sync::PoisonError<T>> for Error<ErrTag> {
634 fn from(_e: std::sync::PoisonError<T>) -> Self {
635 Error::Local(ErrMsg {
636 tags: &[ErrTag::Poisoned],
637 msg: String::new(),
638 })
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645 use crate::{
646 errmsg,
647 Outcome,
648 };
649 use std::{
650 fmt::write,
651 str::FromStr,
652 };
653
654 fn return_fmt_error() -> Outcome<i32> {
655 let mut output = String::new();
656 ok!(write(&mut output, format_args!("Hello {}!", "world")));
657 Ok(42)
658 }
659
660 #[test]
661 fn test_debug_carries_no_terminal_escapes() -> Outcome<()> {
662 // Three deep: a local error, wrapped, wrapped again.
663 fn innermost() -> Outcome<()> {
664 Err(err!("The tree region does not hash to what was signed."; Invalid, Input))
665 }
666 fn middle() -> Outcome<()> {
667 res!(innermost());
668 Ok(())
669 }
670 fn outer() -> Outcome<()> {
671 res!(middle());
672 Ok(())
673 }
674 let e = match outer() {
675 Ok(()) => return Err(err!("The error was supposed to propagate."; Bug)),
676 Err(e) => e,
677 };
678
679 let debug = fmt!("{:?}", e);
680 assert!(
681 !debug.contains('\u{1b}'),
682 "The Debug form carries an ANSI escape, so it is not plain: {:?}", debug,
683 );
684 assert!(
685 debug.contains("does not hash to what was signed"),
686 "and the innermost message must still be in it: {}", debug,
687 );
688
689 // The Display form is for a console, and may colour itself all it likes.
690 Ok(())
691 }
692
693 #[test]
694 fn test_plain_is_words_and_nothing_else() -> Outcome<()> {
695 // The shape of a real rejection: a detailed innermost error, wrapped by frames that add
696 // context, and wrapped again by one that adds none.
697 fn innermost() -> Outcome<()> {
698 Err(err!(
699 "The 1895 byte tree region hashes to 1507362a, but the envelope declares \
700 387a4f57."; Invalid, Input, Mismatch))
701 }
702 fn middle() -> Outcome<()> {
703 match innermost() {
704 Ok(()) => Ok(()),
705 Err(e) => Err(err!(e, "The document could not be read."; Invalid)),
706 }
707 }
708 fn outer() -> Outcome<()> {
709 res!(middle()); // Adds a frame carrying no words of its own.
710 Ok(())
711 }
712 let e = match outer() {
713 Ok(()) => return Err(err!("The error was supposed to propagate."; Bug)),
714 Err(e) => e,
715 };
716
717 let plain = e.plain();
718
719 // What a developer needs, and a reader does not.
720 assert!(!plain.contains(".rs:"), "plain names a source file: {}", plain);
721 assert!(!plain.contains("LocalErr"), "plain names a frame: {}", plain);
722 assert!(!plain.contains("UpstreamErr"), "plain names a frame: {}", plain);
723 assert!(!plain.contains('['), "plain carries its tags: {}", plain);
724 assert!(!plain.contains('\u{1b}'), "plain carries an ANSI escape: {}", plain);
725
726 // What a reader needs, and every word of it.
727 assert!(
728 plain.contains("The document could not be read."),
729 "the context is missing: {}", plain,
730 );
731 assert!(
732 plain.contains("hashes to 1507362a, but the envelope declares 387a4f57."),
733 "the detail is missing: {}", plain,
734 );
735 // Context first, detail after: the reader is told what failed before being told how.
736 let ctx = plain.find("could not be read");
737 let det = plain.find("hashes to");
738 assert!(ctx < det, "the detail came before the context: {}", plain);
739
740 // The frame that carried no words of its own contributed none.
741 assert_eq!(e.msgs().len(), 2, "a wordless frame put something in: {:?}", e.msgs());
742 Ok(())
743 }
744
745 #[test]
746 fn test_a_location_is_told_from_a_message_that_merely_holds_a_colon() {
747 assert_eq!(without_location("src/doc.rs:95: The tree is short."), "The tree is short.");
748 assert_eq!(without_location("src/doc.rs:95"), "");
749 // A message of somebody else's, which owes the macro nothing.
750 assert_eq!(without_location("error: the file is missing"), "error: the file is missing");
751 assert_eq!(without_location("Note: 3 of 4 failed"), "Note: 3 of 4 failed");
752 assert_eq!(without_location("no colon here"), "no colon here");
753 }
754
755 #[test]
756 fn test_errctx() -> Outcome<()> {
757 let m = errmsg!("The meaning of life is {}", 42);
758 println!("ErrMsg = {}", m);
759 let m = errmsg!();
760 println!("ErrMsg = {}", m);
761 let n = 41;
762 let e = Error::Local(ErrMsg {
763 tags: &[ErrTag::Invalid],
764 msg: errmsg!("The meaning of life is not quite {}", n),
765 });
766 println!("This is a test of an Error: {}", e);
767
768 Ok(())
769 }
770
771 #[test]
772 fn test_errprop_00() -> Outcome<()> {
773 let res0 = Outcome::Ok(());
774 let res1 = res!(res0);
775 msg!("{:?}", res1);
776 Ok(())
777 }
778
779 #[test]
780 fn test_err_00() -> Outcome<()> {
781 let e0 = err!(fmt!("A test {}", 42); String, Invalid);
782 msg!("{:?}", e0);
783 let e1 = Error::Local(ErrMsg { tags: &[ErrTag::IO, ErrTag::Invalid], msg: errmsg!("A test 42") });
784 let e2 = Error::Upstream(Arc::new(e1), ErrMsg { tags: &[ErrTag::IO, ErrTag::File], msg: errmsg!() });
785 let e3 = Error::Upstream(Arc::new(e2), ErrMsg { tags: &[ErrTag::Conversion], msg: errmsg!() });
786 msg!("\n{:?}", e3);
787 Ok(())
788 }
789
790 #[test]
791 fn test_err_01() -> Outcome<()> {
792 match u8::from_str("-1") {
793 Err(e0) => {
794 msg!("{}", e0);
795 let e1 = err!(e0, errmsg!("A test");
796 Decode, String, Invalid, Input);
797 msg!("{}", e1);
798 let e2 = err!(e1, errmsg!("Another level"); Bug);
799 msg!("{}", e2);
800 },
801 Ok(_) => (),
802 }
803
804 Ok(())
805 }
806
807 #[test]
808 fn test_tags_are_gathered_through_every_wrapping_frame() -> Outcome<()> {
809 fn inner() -> Outcome<()> {
810 Err(err!("The store is at its cap."; Excessive, LimitReached))
811 }
812 fn middle() -> Outcome<()> {
813 res!(inner()); // A frame with no tags of its own.
814 Ok(())
815 }
816 fn outer() -> Outcome<()> {
817 res!(middle(), IO);
818 Ok(())
819 }
820 let e = match outer() {
821 Ok(()) => return Err(err!("The error was supposed to propagate."; Bug)),
822 Err(e) => e,
823 };
824 let tags = e.tags();
825 assert_eq!(tags, vec![ErrTag::IO, ErrTag::Excessive, ErrTag::LimitReached], "outermost first");
826 Ok(())
827 }
828
829 #[test]
830 fn test_tags_pass_through_a_foreign_error_that_wraps_one_of_ours() -> Outcome<()> {
831 // Somebody else's error type, whose source is one of ours.
832 #[derive(Debug)]
833 struct Foreign(Error<ErrTag>);
834 impl fmt::Display for Foreign {
835 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836 write!(f, "{}", self.0.plain())
837 }
838 }
839 impl std::error::Error for Foreign {
840 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
841 Some(&self.0)
842 }
843 }
844 let fault = err!("A chunk took the body past its limit."; Input, TooBig);
845 let e = err!(Foreign(fault), "The answer could not be read."; IO);
846 let e = Error::Upstream(Arc::new(e), ErrMsg { tags: &[], msg: errmsg!() });
847 assert_eq!(e.tags(), vec![ErrTag::IO, ErrTag::Input, ErrTag::TooBig],
848 "the walk goes on through the foreign frame to the error it wraps");
849 Ok(())
850 }
851}
852
853impl GenTag for ErrTag {}
854
855impl fmt::Display for ErrTag {
856 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
857 write!(f, "{:?}", self)
858 }
859}
860
861impl Default for ErrTag {
862 fn default() -> Self {
863 Self::Unknown
864 }
865}