color_eyre/section/
github.rs1use 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 #[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}