Skip to main content

color_eyre/section/
github.rs

1use crate::writers::DisplayExt;
2use backtrace::Backtrace;
3use std::{fmt, panic::Location};
4#[cfg(feature = "capture-spantrace")]
5use tracing_error::SpanTrace;
6use url::Url;
7
8type Display<'a> = Box<dyn std::fmt::Display + Send + Sync + 'a>;
9
10pub(crate) struct IssueSection<'a> {
11    url: &'a str,
12    msg: &'a str,
13    location: Option<&'a Location<'a>>,
14    backtrace: Option<&'a Backtrace>,
15    #[cfg(feature = "capture-spantrace")]
16    span_trace: Option<&'a SpanTrace>,
17    metadata: &'a [(String, Display<'a>)],
18}
19
20impl<'a> IssueSection<'a> {
21    pub(crate) fn new(url: &'a str, msg: &'a str) -> Self {
22        IssueSection {
23            url,
24            msg,
25            location: None,
26            backtrace: None,
27            #[cfg(feature = "capture-spantrace")]
28            span_trace: None,
29            metadata: &[],
30        }
31    }
32
33    pub(crate) fn with_location(mut self, location: impl Into<Option<&'a Location<'a>>>) -> Self {
34        self.location = location.into();
35        self
36    }
37
38    pub(crate) fn with_backtrace(mut self, backtrace: impl Into<Option<&'a Backtrace>>) -> Self {
39        self.backtrace = backtrace.into();
40        self
41    }
42
43    #[cfg(feature = "capture-spantrace")]
44    pub(crate) fn with_span_trace(mut self, span_trace: impl Into<Option<&'a SpanTrace>>) -> Self {
45        self.span_trace = span_trace.into();
46        self
47    }
48
49    pub(crate) fn with_metadata(mut self, metadata: &'a [(String, Display<'a>)]) -> Self {
50        self.metadata = metadata;
51        self
52    }
53}
54
55impl fmt::Display for IssueSection<'_> {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        let location = self
58            .location
59            .map(|loc| ("location".to_string(), Box::new(loc) as _));
60        let metadata = self.metadata.iter().chain(location.as_ref());
61        let metadata = MetadataSection { metadata }.to_string();
62        let mut body = Body::new();
63        body.push_section("Error", ConsoleSection(self.msg))?;
64
65        if !self.metadata.is_empty() {
66            body.push_section("Metadata", metadata)?;
67        }
68
69        #[cfg(feature = "capture-spantrace")]
70        if let Some(st) = self.span_trace {
71            body.push_section(
72                "SpanTrace",
73                Collapsed(ConsoleSection(st.with_header("SpanTrace:\n"))),
74            )?;
75        }
76
77        if let Some(bt) = self.backtrace {
78            body.push_section(
79                "Backtrace",
80                Collapsed(ConsoleSection(
81                    DisplayFromDebug(bt).with_header("Backtrace:\n"),
82                )),
83            )?;
84        }
85
86        let url_result = Url::parse_with_params(
87            self.url,
88            &[("title", "<autogenerated-issue>"), ("body", &body.body)],
89        );
90
91        let url: &dyn fmt::Display = match &url_result {
92            Ok(url_struct) => url_struct,
93            Err(_) => &self.url,
94        };
95
96        url.with_header("Consider reporting this error using this URL: ")
97            .fmt(f)
98    }
99}
100
101struct Body {
102    body: String,
103}
104
105impl Body {
106    fn new() -> Self {
107        Body {
108            body: String::new(),
109        }
110    }
111    fn push_section<T>(&mut self, header: &'static str, section: T) -> fmt::Result
112    where
113        T: fmt::Display,
114    {
115        use std::fmt::Write;
116
117        let separator = if self.body.is_empty() { "" } else { "\n\n" };
118        let header = header
119            .with_header("## ")
120            .with_header(separator)
121            .with_footer("\n");
122
123        write!(&mut self.body, "{}", section.with_header(header))
124    }
125}
126
127struct MetadataSection<T> {
128    metadata: T,
129}
130
131impl<'a, T> MetadataSection<T>
132where
133    T: IntoIterator<Item = &'a (String, Display<'a>)>,
134{
135    // This is implemented as a free functions so it can consume the `metadata`
136    // iterator, rather than being forced to leave it unmodified if its behind a
137    // `&self` shared reference via the Display trait
138    #[allow(clippy::inherent_to_string, clippy::wrong_self_convention)]
139    fn to_string(self) -> String {
140        use std::fmt::Write;
141
142        let mut out = String::new();
143        let f = &mut out;
144
145        writeln!(f, "|key|value|").expect("writing to a string doesn't panic");
146        writeln!(f, "|--|--|").expect("writing to a string doesn't panic");
147
148        for (key, value) in self.metadata {
149            writeln!(f, "|**{}**|{}|", key, value).expect("writing to a string doesn't panic");
150        }
151
152        out
153    }
154}
155
156struct ConsoleSection<T>(T);
157
158impl<T> fmt::Display for ConsoleSection<T>
159where
160    T: fmt::Display,
161{
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        (&self.0).with_header("```\n").with_footer("\n```").fmt(f)
164    }
165}
166
167struct Collapsed<T>(T);
168
169impl<T> fmt::Display for Collapsed<T>
170where
171    T: fmt::Display,
172{
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        (&self.0)
175            .with_header("\n<details>\n\n")
176            .with_footer("\n</details>")
177            .fmt(f)
178    }
179}
180
181struct DisplayFromDebug<T>(T);
182
183impl<T> fmt::Display for DisplayFromDebug<T>
184where
185    T: fmt::Debug,
186{
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        self.0.fmt(f)
189    }
190}