1use std::fmt::{self, Display, Formatter};
2use std::io::Write;
3
4use clap::CommandFactory;
5use itertools::{Itertools, Position};
6use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
7
8use crate::prelude::ClingFinished;
9use crate::Run;
10
11pub trait CliErrorHandler {
12 type Output;
13 fn unwrap_or_exit(self) -> Self::Output;
14 fn then_exit(self) -> !;
15}
16
17pub enum CliError {
22 InvalidHandler(String),
23 Failed,
24 FailedWithMessage(String),
25 FailedWithMessageAndCode(String, u8),
26 ClapError(clap::Error),
27 InputString,
28 Other(anyhow::Error),
29 OtherWithCode(anyhow::Error, u8),
30}
31
32impl std::error::Error for CliError {}
33
34impl From<anyhow::Error> for CliError {
38 fn from(value: anyhow::Error) -> Self {
39 CliError::Other(value)
40 }
41}
42
43impl From<std::io::Error> for CliError {
44 fn from(value: std::io::Error) -> Self {
45 CliError::Other(value.into())
46 }
47}
48
49impl From<clap::Error> for CliError {
50 fn from(value: clap::Error) -> Self {
51 CliError::ClapError(value)
52 }
53}
54
55impl<T, E> CliErrorHandler for Result<T, E>
56where
57 E: Into<CliError>,
58{
59 type Output = T;
60
61 fn unwrap_or_exit(self) -> T {
64 match self {
65 | Ok(x) => x,
66 | Err(e) => {
67 let e = e.into();
68 e.print().unwrap();
69 e.exit()
70 }
71 }
72 }
73
74 fn then_exit(self) -> ! {
77 match self {
78 | Ok(_) => std::process::exit(0),
79 | Err(e) => {
80 let e = e.into();
81 e.print().unwrap();
82 e.exit()
83 }
84 }
85 }
86}
87
88impl Display for CliError {
89 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
90 match self {
91 | CliError::ClapError(e) => {
92 write!(f, "{}", e)
94 }
95 | CliError::Failed => {
96 write!(f, "Failed!")
97 }
98 | CliError::FailedWithMessage(e) => {
99 write!(f, "Failed: {}", e)
100 }
101 | CliError::FailedWithMessageAndCode(e, _) => {
102 write!(f, "Error: {}", e)
103 }
104 | CliError::Other(e) => {
105 write!(f, "Error: {:#}", e)
106 }
107 | CliError::OtherWithCode(e, _) => {
108 write!(f, "Error: {:#}", e)
109 }
110 | CliError::InputString => {
111 write!(f, "Input string cannot be parsed as UNIX shell command")
112 }
113 #[allow(unused_variables)]
114 | CliError::InvalidHandler(msg) => {
115 #[cfg(not(debug_assertions))]
116 let r = write!(
117 f,
118 "\n\n** Cling Handler Design Error **\n\n{}",
119 "Detailed error message available only in debug builds.",
120 );
121 #[cfg(debug_assertions)]
122 let r = write!(
123 f,
124 "\n\n** Cling Handler Design Error **\n\n{}",
125 msg
126 );
127 r
128 }
129 }
130 }
131}
132
133impl std::fmt::Debug for CliError {
134 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
135 write!(f, "{}", self)
136 }
137}
138
139impl CliError {
140 pub fn print(&self) -> std::io::Result<()> {
142 let mut stderr = StandardStream::stderr(ColorChoice::Auto);
143 match self {
144 | CliError::ClapError(e) => {
145 e.print()
147 }
148 | CliError::Failed => {
149 print_formatted_error(&mut stderr, "Aborted!", "")
150 }
151 | CliError::FailedWithMessage(e) => {
152 print_formatted_error(&mut stderr, "", e)
153 }
154 | CliError::FailedWithMessageAndCode(e, _) => {
155 print_formatted_error(&mut stderr, "", e)
156 }
157 | CliError::Other(e) => {
158 print_anyhow_error(&mut stderr, "Error: ", e)
159 }
160 | CliError::OtherWithCode(e, _) => {
161 print_anyhow_error(&mut stderr, "Error: ", e)
162 }
163 | e @ CliError::InputString => {
164 print_formatted_error(&mut stderr, "", &e.to_string())
165 }
166 #[allow(unused_variables)]
167 | CliError::InvalidHandler(msg) => {
168 #[cfg(not(debug_assertions))]
169 let r = print_formatted_error(
170 &mut stderr,
171 "\n\n** Cling Handler Design Error **\n\n",
172 "Detailed error message available only in debug builds.",
173 );
174 #[cfg(debug_assertions)]
175 let r = print_formatted_error(
176 &mut stderr,
177 "\n\n** Cling Handler Design Error **\n\n",
178 msg,
179 );
180 r
181 }
182 }
183 }
184
185 pub fn exit_code(&self) -> u8 {
187 match self {
188 | CliError::FailedWithMessageAndCode(_, code) => *code,
189 | CliError::OtherWithCode(_, code) => *code,
190 | CliError::ClapError(e) => {
193 let code = e.exit_code();
194 code.try_into().unwrap_or(255)
195 }
196 | _ => 1,
197 }
198 }
199
200 pub fn exit(self) -> ! {
202 std::process::exit(self.exit_code() as i32)
203 }
204
205 pub fn into_finished<T: Run + clap::Parser>(self) -> ClingFinished<T> {
206 Into::into(self)
207 }
208}
209
210static_assertions::assert_impl_all!(CliError: Send, Sync);
211
212fn print_formatted_error(
213 f: &mut StandardStream,
214 heading: &str,
215 msg: &str,
216) -> std::io::Result<()> {
217 f.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
218 write!(f, "{}", heading)?;
219 f.reset()?;
220 writeln!(f, "{}", msg)?;
221 Ok(())
222}
223
224fn print_anyhow_error(
225 f: &mut StandardStream,
226 heading: &str,
227 err: &anyhow::Error,
228) -> std::io::Result<()> {
229 f.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
230 write!(f, "{}", heading)?;
231 f.reset()?;
232 writeln!(f, "{}", err)?;
233 err.chain()
234 .skip(1)
235 .with_position()
236 .for_each(|(position, cause)| {
237 if position == Position::First {
238 let _ =
239 f.set_color(ColorSpec::new().set_fg(Some(Color::Magenta)));
240 let _ = writeln!(f, "");
241 let _ = writeln!(f, "Caused by:");
242 let _ = f.reset();
243 }
244 let symbol = if position == Position::Last {
245 "└─"
246 } else {
247 "├─"
248 };
249 let _ =
250 f.set_color(ColorSpec::new().set_italic(true).set_dimmed(true));
251 let _ = write!(f, " {} ", symbol);
252 let _ = f.reset();
253 let _ = writeln!(f, "{}", cause);
254 });
255 Ok(())
256}
257
258pub(crate) fn format_clap_error<I: CommandFactory>(
259 err: clap::Error,
260) -> clap::Error {
261 let mut cmd = I::command();
262 err.format(&mut cmd)
263}