1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! Example of using `set_debug_hook` to create a custom JSON `Debug` implementation for `Report`

use std::{
    collections::hash_map::{Entry, HashMap},
    error::Error,
    fmt,
};

use error_stack::{bail, AttachmentKind, FrameKind, Report, Result, ResultExt};
use serde::Serialize;

#[derive(Debug)]
enum MapError {
    Occupied { key: &'static str, value: u64 },
}

impl fmt::Display for MapError {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Occupied { key, value } => {
                write!(fmt, "Entry \"{key}\" is already occupied by {value}")
            }
        }
    }
}

impl Error for MapError {}

fn create_new_entry(
    map: &mut HashMap<&str, u64>,
    key: &'static str,
    value: u64,
) -> Result<(), MapError> {
    match map.entry(key) {
        Entry::Occupied(entry) => {
            // `bail!` returns `Err(Report<MapError>)` constructed from its parameters
            bail!(MapError::Occupied {
                key,
                value: *entry.get()
            })
        }
        Entry::Vacant(entry) => {
            entry.insert(value);
        }
    }
    Ok(())
}

fn main() -> Result<(), MapError> {
    // This hook will be executed instead of the default implementation when `Debug` is called
    Report::set_debug_hook(|report, fmt| {
        #[derive(Serialize)]
        struct Context {
            context: String,
            attachments: Vec<String>,
        }

        let mut output = Vec::new();
        let mut attachments = Vec::new();

        for frame in report.frames() {
            match frame.kind() {
                FrameKind::Context(context) => {
                    output.push(Context {
                        context: context.to_string(),
                        attachments: attachments.clone(),
                    });
                    attachments.clear();
                }
                FrameKind::Attachment(AttachmentKind::Printable(attachment)) => {
                    attachments.push(attachment.to_string());
                }
                FrameKind::Attachment(_) => {}
            }
        }

        if fmt.alternate() {
            fmt.write_str(&serde_json::to_string_pretty(&output).expect("Could not format report"))
        } else {
            fmt.write_str(&serde_json::to_string(&output).expect("Could not format report"))
        }
    })
    .expect("Hook was set twice");

    let mut config = HashMap::default();

    // Create an entry with "foo" as key
    create_new_entry(&mut config, "foo", 1).attach_printable("Could not create new entry")?;

    // Purposefully cause an error by attempting to create another entry with "foo" as key
    create_new_entry(&mut config, "foo", 2).attach_printable("Could not create new entry")?;

    // Will output something like
    // ```json
    // [
    //   {
    //     "context": "Entry \"foo\" is already occupied by 1",
    //     "attachments": [
    //       "Could not create new entry"
    //     ]
    //   }
    // ]
    // ```

    Ok(())
}