1 use crate::common::*;
2 
3 #[derive(Debug)]
4 pub(crate) enum Error<'src> {
5   ArgumentCountMismatch {
6     recipe: &'src str,
7     parameters: Vec<Parameter<'src>>,
8     found: usize,
9     min: usize,
10     max: usize,
11   },
12   Backtick {
13     token: Token<'src>,
14     output_error: OutputError,
15   },
16   ChooserInvoke {
17     shell_binary: String,
18     shell_arguments: String,
19     chooser: OsString,
20     io_error: io::Error,
21   },
22   ChooserRead {
23     chooser: OsString,
24     io_error: io::Error,
25   },
26   ChooserStatus {
27     chooser: OsString,
28     status: ExitStatus,
29   },
30   ChooserWrite {
31     chooser: OsString,
32     io_error: io::Error,
33   },
34   Code {
35     recipe: &'src str,
36     line_number: Option<usize>,
37     code: i32,
38   },
39   CommandInvoke {
40     binary: OsString,
41     arguments: Vec<OsString>,
42     io_error: io::Error,
43   },
44   CommandStatus {
45     binary: OsString,
46     arguments: Vec<OsString>,
47     status: ExitStatus,
48   },
49   Compile {
50     compile_error: CompileError<'src>,
51   },
52   Config {
53     config_error: ConfigError,
54   },
55   Cygpath {
56     recipe: &'src str,
57     output_error: OutputError,
58   },
59   DefaultRecipeRequiresArguments {
60     recipe: &'src str,
61     min_arguments: usize,
62   },
63   Dotenv {
64     dotenv_error: dotenv::Error,
65   },
66   DumpJson {
67     serde_json_error: serde_json::Error,
68   },
69   EditorInvoke {
70     editor: OsString,
71     io_error: io::Error,
72   },
73   EditorStatus {
74     editor: OsString,
75     status: ExitStatus,
76   },
77   EvalUnknownVariable {
78     variable: String,
79     suggestion: Option<Suggestion<'src>>,
80   },
81   FormatCheckFoundDiff,
82   FunctionCall {
83     function: Name<'src>,
84     message: String,
85   },
86   InitExists {
87     justfile: PathBuf,
88   },
89   Internal {
90     message: String,
91   },
92   Io {
93     recipe: &'src str,
94     io_error: io::Error,
95   },
96   Load {
97     path: PathBuf,
98     io_error: io::Error,
99   },
100   NoChoosableRecipes,
101   NoRecipes,
102   RegexCompile {
103     source: regex::Error,
104   },
105   Search {
106     search_error: SearchError,
107   },
108   Shebang {
109     recipe: &'src str,
110     command: String,
111     argument: Option<String>,
112     io_error: io::Error,
113   },
114   Signal {
115     recipe: &'src str,
116     line_number: Option<usize>,
117     signal: i32,
118   },
119   TmpdirIo {
120     recipe: &'src str,
121     io_error: io::Error,
122   },
123   Unknown {
124     recipe: &'src str,
125     line_number: Option<usize>,
126   },
127   UnknownOverrides {
128     overrides: Vec<String>,
129   },
130   UnknownRecipes {
131     recipes: Vec<String>,
132     suggestion: Option<Suggestion<'src>>,
133   },
134   Unstable {
135     message: String,
136   },
137   WriteJustfile {
138     justfile: PathBuf,
139     io_error: io::Error,
140   },
141 }
142 
143 impl<'src> Error<'src> {
code(&self) -> Option<i32>144   pub(crate) fn code(&self) -> Option<i32> {
145     match self {
146       Self::Code { code, .. }
147       | Self::Backtick {
148         output_error: OutputError::Code(code),
149         ..
150       } => Some(*code),
151       Self::ChooserStatus { status, .. } | Self::EditorStatus { status, .. } => status.code(),
152       _ => None,
153     }
154   }
155 
context(&self) -> Option<Token<'src>>156   fn context(&self) -> Option<Token<'src>> {
157     match self {
158       Self::Backtick { token, .. } => Some(*token),
159       Self::Compile { compile_error } => Some(compile_error.context()),
160       Self::FunctionCall { function, .. } => Some(function.token()),
161       _ => None,
162     }
163   }
164 
internal(message: impl Into<String>) -> Self165   pub(crate) fn internal(message: impl Into<String>) -> Self {
166     Self::Internal {
167       message: message.into(),
168     }
169   }
170 }
171 
172 impl<'src> From<CompileError<'src>> for Error<'src> {
from(compile_error: CompileError<'src>) -> Self173   fn from(compile_error: CompileError<'src>) -> Self {
174     Self::Compile { compile_error }
175   }
176 }
177 
178 impl<'src> From<ConfigError> for Error<'src> {
from(config_error: ConfigError) -> Self179   fn from(config_error: ConfigError) -> Self {
180     Self::Config { config_error }
181   }
182 }
183 
184 impl<'src> From<dotenv::Error> for Error<'src> {
from(dotenv_error: dotenv::Error) -> Error<'src>185   fn from(dotenv_error: dotenv::Error) -> Error<'src> {
186     Self::Dotenv { dotenv_error }
187   }
188 }
189 
190 impl<'src> From<SearchError> for Error<'src> {
from(search_error: SearchError) -> Self191   fn from(search_error: SearchError) -> Self {
192     Self::Search { search_error }
193   }
194 }
195 
196 impl<'src> ColorDisplay for Error<'src> {
fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result197   fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result {
198     use Error::*;
199 
200     write!(
201       f,
202       "{}: {}",
203       color.error().paint("error"),
204       color.message().prefix()
205     )?;
206 
207     match self {
208       ArgumentCountMismatch {
209         recipe,
210         found,
211         min,
212         max,
213         ..
214       } => {
215         if min == max {
216           let expected = min;
217           write!(
218             f,
219             "Recipe `{}` got {} {} but {}takes {}",
220             recipe,
221             found,
222             Count("argument", *found),
223             if expected < found { "only " } else { "" },
224             expected
225           )?;
226         } else if found < min {
227           write!(
228             f,
229             "Recipe `{}` got {} {} but takes at least {}",
230             recipe,
231             found,
232             Count("argument", *found),
233             min
234           )?;
235         } else if found > max {
236           write!(
237             f,
238             "Recipe `{}` got {} {} but takes at most {}",
239             recipe,
240             found,
241             Count("argument", *found),
242             max
243           )?;
244         }
245       }
246       Backtick { output_error, .. } => match output_error {
247         OutputError::Code(code) => {
248           write!(f, "Backtick failed with exit code {}", code)?;
249         }
250         OutputError::Signal(signal) => {
251           write!(f, "Backtick was terminated by signal {}", signal)?;
252         }
253         OutputError::Unknown => {
254           write!(f, "Backtick failed for an unknown reason")?;
255         }
256         OutputError::Io(io_error) => {
257           match io_error.kind() {
258             io::ErrorKind::NotFound => write!(
259               f,
260               "Backtick could not be run because just could not find the shell:\n{}",
261               io_error
262             ),
263             io::ErrorKind::PermissionDenied => write!(
264               f,
265               "Backtick could not be run because just could not run the shell:\n{}",
266               io_error
267             ),
268             _ => write!(
269               f,
270               "Backtick could not be run because of an IO error while launching the shell:\n{}",
271               io_error
272             ),
273           }?;
274         }
275         OutputError::Utf8(utf8_error) => {
276           write!(
277             f,
278             "Backtick succeeded but stdout was not utf8: {}",
279             utf8_error
280           )?;
281         }
282       },
283       ChooserInvoke {
284         shell_binary,
285         shell_arguments,
286         chooser,
287         io_error,
288       } => {
289         write!(
290           f,
291           "Chooser `{} {} {}` invocation failed: {}",
292           shell_binary,
293           shell_arguments,
294           chooser.to_string_lossy(),
295           io_error,
296         )?;
297       }
298       ChooserRead { chooser, io_error } => {
299         write!(
300           f,
301           "Failed to read output from chooser `{}`: {}",
302           chooser.to_string_lossy(),
303           io_error
304         )?;
305       }
306       ChooserStatus { chooser, status } => {
307         write!(
308           f,
309           "Chooser `{}` failed: {}",
310           chooser.to_string_lossy(),
311           status
312         )?;
313       }
314       ChooserWrite { chooser, io_error } => {
315         write!(
316           f,
317           "Failed to write to chooser `{}`: {}",
318           chooser.to_string_lossy(),
319           io_error
320         )?;
321       }
322       Code {
323         recipe,
324         line_number,
325         code,
326       } => {
327         if let Some(n) = line_number {
328           write!(
329             f,
330             "Recipe `{}` failed on line {} with exit code {}",
331             recipe, n, code
332           )?;
333         } else {
334           write!(f, "Recipe `{}` failed with exit code {}", recipe, code)?;
335         }
336       }
337       CommandInvoke {
338         binary,
339         arguments,
340         io_error,
341       } => {
342         write!(
343           f,
344           "Failed to invoke {}: {}",
345           iter::once(binary)
346             .chain(arguments)
347             .map(|value| Enclosure::tick(value.to_string_lossy()).to_string())
348             .collect::<Vec<String>>()
349             .join(" "),
350           io_error,
351         )?;
352       }
353       CommandStatus {
354         binary,
355         arguments,
356         status,
357       } => {
358         write!(
359           f,
360           "Command {} failed: {}",
361           iter::once(binary)
362             .chain(arguments)
363             .map(|value| Enclosure::tick(value.to_string_lossy()).to_string())
364             .collect::<Vec<String>>()
365             .join(" "),
366           status,
367         )?;
368       }
369       Compile { compile_error } => Display::fmt(compile_error, f)?,
370       Config { config_error } => Display::fmt(config_error, f)?,
371       Cygpath {
372         recipe,
373         output_error,
374       } => match output_error {
375         OutputError::Code(code) => {
376           write!(
377             f,
378             "Cygpath failed with exit code {} while translating recipe `{}` shebang interpreter \
379              path",
380             code, recipe
381           )?;
382         }
383         OutputError::Signal(signal) => {
384           write!(
385             f,
386             "Cygpath terminated by signal {} while translating recipe `{}` shebang interpreter \
387              path",
388             signal, recipe
389           )?;
390         }
391         OutputError::Unknown => {
392           write!(
393             f,
394             "Cygpath experienced an unknown failure while translating recipe `{}` shebang \
395              interpreter path",
396             recipe
397           )?;
398         }
399         OutputError::Io(io_error) => {
400           match io_error.kind() {
401             io::ErrorKind::NotFound => write!(
402               f,
403               "Could not find `cygpath` executable to translate recipe `{}` shebang interpreter \
404                path:\n{}",
405               recipe, io_error
406             ),
407             io::ErrorKind::PermissionDenied => write!(
408               f,
409               "Could not run `cygpath` executable to translate recipe `{}` shebang interpreter \
410                path:\n{}",
411               recipe, io_error
412             ),
413             _ => write!(f, "Could not run `cygpath` executable:\n{}", io_error),
414           }?;
415         }
416         OutputError::Utf8(utf8_error) => {
417           write!(
418             f,
419             "Cygpath successfully translated recipe `{}` shebang interpreter path, but output was \
420              not utf8: {}",
421             recipe, utf8_error
422           )?;
423         }
424       },
425       DefaultRecipeRequiresArguments {
426         recipe,
427         min_arguments,
428       } => {
429         write!(
430           f,
431           "Recipe `{}` cannot be used as default recipe since it requires at least {} {}.",
432           recipe,
433           min_arguments,
434           Count("argument", *min_arguments),
435         )?;
436       }
437       Dotenv { dotenv_error } => {
438         write!(f, "Failed to load environment file: {}", dotenv_error)?;
439       }
440       DumpJson { serde_json_error } => {
441         write!(f, "Failed to dump JSON to stdout: {}", serde_json_error)?;
442       }
443       EditorInvoke { editor, io_error } => {
444         write!(
445           f,
446           "Editor `{}` invocation failed: {}",
447           editor.to_string_lossy(),
448           io_error
449         )?;
450       }
451       EditorStatus { editor, status } => {
452         write!(
453           f,
454           "Editor `{}` failed: {}",
455           editor.to_string_lossy(),
456           status
457         )?;
458       }
459       EvalUnknownVariable {
460         variable,
461         suggestion,
462       } => {
463         write!(f, "Justfile does not contain variable `{}`.", variable,)?;
464         if let Some(suggestion) = *suggestion {
465           write!(f, "\n{}", suggestion)?;
466         }
467       }
468       FormatCheckFoundDiff => {
469         write!(f, "Formatted justfile differs from original.")?;
470       }
471       FunctionCall { function, message } => {
472         write!(
473           f,
474           "Call to function `{}` failed: {}",
475           function.lexeme(),
476           message
477         )?;
478       }
479       InitExists { justfile } => {
480         write!(f, "Justfile `{}` already exists", justfile.display())?;
481       }
482       Internal { message } => {
483         write!(
484           f,
485           "Internal runtime error, this may indicate a bug in just: {} \
486            consider filing an issue: https://github.com/casey/just/issues/new",
487           message
488         )?;
489       }
490       Io { recipe, io_error } => {
491         match io_error.kind() {
492           io::ErrorKind::NotFound => write!(
493             f,
494             "Recipe `{}` could not be run because just could not find the shell: {}",
495             recipe, io_error
496           ),
497           io::ErrorKind::PermissionDenied => write!(
498             f,
499             "Recipe `{}` could not be run because just could not run the shell: {}",
500             recipe, io_error
501           ),
502           _ => write!(
503             f,
504             "Recipe `{}` could not be run because of an IO error while launching the shell: {}",
505             recipe, io_error
506           ),
507         }?;
508       }
509       Load { io_error, path } => {
510         write!(
511           f,
512           "Failed to read justfile at `{}`: {}",
513           path.display(),
514           io_error
515         )?;
516       }
517       NoChoosableRecipes => {
518         write!(f, "Justfile contains no choosable recipes.")?;
519       }
520       NoRecipes => {
521         write!(f, "Justfile contains no recipes.")?;
522       }
523       RegexCompile { source } => {
524         write!(f, "{}", source)?;
525       }
526       Search { search_error } => Display::fmt(search_error, f)?,
527       Shebang {
528         recipe,
529         command,
530         argument,
531         io_error,
532       } => {
533         if let Some(argument) = argument {
534           write!(
535             f,
536             "Recipe `{}` with shebang `#!{} {}` execution error: {}",
537             recipe, command, argument, io_error
538           )?;
539         } else {
540           write!(
541             f,
542             "Recipe `{}` with shebang `#!{}` execution error: {}",
543             recipe, command, io_error
544           )?;
545         }
546       }
547       Signal {
548         recipe,
549         line_number,
550         signal,
551       } => {
552         if let Some(n) = line_number {
553           write!(
554             f,
555             "Recipe `{}` was terminated on line {} by signal {}",
556             recipe, n, signal
557           )?;
558         } else {
559           write!(f, "Recipe `{}` was terminated by signal {}", recipe, signal)?;
560         }
561       }
562       TmpdirIo { recipe, io_error } => write!(
563         f,
564         "Recipe `{}` could not be run because of an IO error while trying to create a temporary \
565          directory or write a file to that directory`:{}",
566         recipe, io_error
567       )?,
568       Unknown {
569         recipe,
570         line_number,
571       } => {
572         if let Some(n) = line_number {
573           write!(
574             f,
575             "Recipe `{}` failed on line {} for an unknown reason",
576             recipe, n
577           )?;
578         } else {
579           write!(f, "Recipe `{}` failed for an unknown reason", recipe)?;
580         }
581       }
582       UnknownOverrides { overrides } => {
583         write!(
584           f,
585           "{} {} overridden on the command line but not present in justfile",
586           Count("Variable", overrides.len()),
587           List::and_ticked(overrides),
588         )?;
589       }
590       UnknownRecipes {
591         recipes,
592         suggestion,
593       } => {
594         write!(
595           f,
596           "Justfile does not contain {} {}.",
597           Count("recipe", recipes.len()),
598           List::or_ticked(recipes),
599         )?;
600         if let Some(suggestion) = *suggestion {
601           write!(f, "\n{}", suggestion)?;
602         }
603       }
604       Unstable { message } => {
605         write!(
606           f,
607           "{} Invoke `just` with the `--unstable` flag to enable unstable features.",
608           message
609         )?;
610       }
611       WriteJustfile { justfile, io_error } => {
612         write!(
613           f,
614           "Failed to write justfile to `{}`: {}",
615           justfile.display(),
616           io_error
617         )?;
618       }
619     }
620 
621     write!(f, "{}", color.message().suffix())?;
622 
623     if let ArgumentCountMismatch {
624       recipe, parameters, ..
625     } = self
626     {
627       writeln!(f)?;
628       write!(
629         f,
630         "{}:\n    just {}",
631         color.message().paint("usage"),
632         recipe
633       )?;
634       for param in parameters {
635         write!(f, " {}", param.color_display(color))?;
636       }
637     }
638 
639     if let Some(token) = self.context() {
640       writeln!(f)?;
641       write!(f, "{}", token.color_display(color.error()))?;
642     }
643 
644     Ok(())
645   }
646 }
647