mz_testdrive/
error.rs
1use std::fmt::{self, Write as _};
20use std::io::{self, IsTerminal, Write};
21use std::path::{Path, PathBuf};
22
23use mz_ore::error::ErrorExt;
24use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
25
26pub struct Error {
33 source: anyhow::Error,
34 location: Option<ErrorLocation>,
35}
36
37impl Error {
38 pub(crate) fn new(source: anyhow::Error, location: Option<ErrorLocation>) -> Self {
39 Error { source, location }
40 }
41
42 pub fn print_error(&self) -> io::Result<()> {
44 let color_choice = if std::io::stdout().is_terminal() {
45 ColorChoice::Auto
46 } else {
47 ColorChoice::Never
48 };
49 let mut stdout = StandardStream::stdout(color_choice);
50 eprintln!("^^^ +++");
51 match &self.location {
52 Some(location) => {
53 let mut color_spec = ColorSpec::new();
54 color_spec.set_bold(true);
55 stdout.set_color(&color_spec)?;
56 if let Some(filename) = &location.filename {
57 write!(
58 &mut stdout,
59 "{}:{}:{}: ",
60 filename.display(),
61 location.line,
62 location.col
63 )?;
64 } else {
65 write!(&mut stdout, "{}:{}: ", location.line, location.col)?;
66 }
67 write_error_heading(&mut stdout, &color_spec)?;
68 writeln!(&mut stdout, "{}", self.source.display_with_causes())?;
69 color_spec.set_bold(false);
70 stdout.set_color(&color_spec)?;
71 write!(&mut stdout, "{}", location.snippet)?;
72 writeln!(&mut stdout, "{}^", " ".repeat(location.col - 1))?;
73 }
74 None => {
75 let color_spec = ColorSpec::new();
76 write_error_heading(&mut stdout, &color_spec)?;
77 writeln!(&mut stdout, "{}", self.source.display_with_causes())?;
78 }
79 }
80 std::io::stdout().flush()
81 }
82}
83
84impl fmt::Display for Error {
85 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86 match &self.location {
87 Some(location) => {
88 if let Some(filename) = &location.filename {
89 write!(
90 f,
91 "{}:{}:{}: ",
92 filename.display(),
93 location.line,
94 location.col
95 )?;
96 } else {
97 write!(f, "{}:{}: ", location.line, location.col)?;
98 }
99 writeln!(f, "{}", self.source.display_with_causes())?;
100 write!(f, "{}", location.snippet)?;
101 writeln!(f, "{}^", " ".repeat(location.col - 1))
102 }
103 None => {
104 write!(f, "{}", self.source.display_with_causes())
105 }
106 }
107 }
108}
109
110fn write_error_heading(stream: &mut StandardStream, color_spec: &ColorSpec) -> io::Result<()> {
111 stream.set_color(color_spec.clone().set_fg(Some(Color::Red)))?;
112 write!(stream, "error: ")?;
113 stream.set_color(color_spec)
114}
115
116impl From<anyhow::Error> for Error {
117 fn from(source: anyhow::Error) -> Error {
118 Error {
119 source,
120 location: None,
121 }
122 }
123}
124
125pub(crate) struct ErrorLocation {
126 filename: Option<PathBuf>,
127 snippet: String,
128 line: usize,
129 col: usize,
130}
131
132impl ErrorLocation {
133 pub(crate) fn new(
134 filename: Option<&Path>,
135 contents: &str,
136 line: usize,
137 col: usize,
138 ) -> ErrorLocation {
139 let mut snippet = String::new();
140 writeln!(&mut snippet, " |").unwrap();
141 for (i, l) in contents.lines().enumerate() {
142 let l_lc = l.to_lowercase();
143 if i >= line {
144 break;
145 } else if l_lc.contains("postgres-") || l_lc.contains("secret") || l_lc.contains("url")
146 {
147 writeln!(
148 &mut snippet,
149 "{:4} | {} ... [rest of line truncated for security]",
150 i + 1,
151 l.get(0..20).unwrap_or(l)
152 )
153 .unwrap();
154 } else if i + 2 >= line {
155 writeln!(&mut snippet, "{:4} | {}", i + 1, l).unwrap();
156 }
157 }
158 write!(&mut snippet, " | ").unwrap();
159
160 ErrorLocation {
161 filename: filename.map(|f| f.to_path_buf()),
162 snippet,
163 line,
164 col,
165 }
166 }
167}
168
169pub(crate) struct PosError {
170 pub(crate) source: anyhow::Error,
171 pub(crate) pos: Option<usize>,
172}
173
174impl PosError {
175 pub(crate) fn new(source: anyhow::Error, pos: usize) -> PosError {
176 PosError {
177 source,
178 pos: Some(pos),
179 }
180 }
181}
182
183impl From<anyhow::Error> for PosError {
184 fn from(source: anyhow::Error) -> PosError {
185 PosError { source, pos: None }
186 }
187}