1use std::fmt;
2use std::process::ExitCode;
3
4use clap::error::ErrorKind as ClapErrorKind;
5use specman::SpecmanError;
6use specman::error::LifecycleError;
7
8const EX_OK: u8 = 0;
9const EX_USAGE: u8 = 64;
10const EX_DATAERR: u8 = 65;
11const EX_SOFTWARE: u8 = 70;
12const EX_OSERR: u8 = 71;
13const EX_CONFIG: u8 = 78;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ExitStatus {
17 Ok,
18 Usage,
19 Data,
20 Io,
21 Config,
22 Software,
23}
24
25impl ExitStatus {
26 pub fn code(self) -> u8 {
27 match self {
28 ExitStatus::Ok => EX_OK,
29 ExitStatus::Usage => EX_USAGE,
30 ExitStatus::Data => EX_DATAERR,
31 ExitStatus::Io => EX_OSERR,
32 ExitStatus::Config => EX_CONFIG,
33 ExitStatus::Software => EX_SOFTWARE,
34 }
35 }
36}
37
38#[derive(Debug)]
39pub struct CliError {
40 message: String,
41 status: ExitStatus,
42}
43
44impl CliError {
45 pub fn new(message: impl Into<String>, status: ExitStatus) -> Self {
46 Self {
47 message: message.into(),
48 status,
49 }
50 }
51
52 pub fn exit_code(&self) -> ExitCode {
53 ExitCode::from(self.status.code())
54 }
55
56 pub fn print(&self) {
57 if !self.message.is_empty() {
58 eprintln!("{}", self.message);
59 }
60 }
61}
62
63impl From<SpecmanError> for CliError {
64 fn from(err: SpecmanError) -> Self {
65 let status = match &err {
66 SpecmanError::Template(_)
67 | SpecmanError::Dependency(_)
68 | SpecmanError::MissingTarget(_) => ExitStatus::Data,
69 SpecmanError::Lifecycle(err) => match err {
70 LifecycleError::DeletionBlocked { .. } => ExitStatus::Data,
71 LifecycleError::PlanTargetMismatch { .. } => ExitStatus::Software,
72 LifecycleError::Context { source, .. } => match source.as_ref() {
73 LifecycleError::DeletionBlocked { .. } => ExitStatus::Data,
74 LifecycleError::PlanTargetMismatch { .. } => ExitStatus::Software,
75 LifecycleError::Context { .. } => ExitStatus::Software,
76 },
77 },
78 SpecmanError::Workspace(_) => ExitStatus::Usage,
79 SpecmanError::Serialization(_) => ExitStatus::Software,
80 SpecmanError::Io(_) => ExitStatus::Io,
81 };
82 CliError::new(err.to_string(), status)
83 }
84}
85
86impl From<clap::Error> for CliError {
87 fn from(err: clap::Error) -> Self {
88 let status = match err.kind() {
89 ClapErrorKind::DisplayHelp | ClapErrorKind::DisplayVersion => ExitStatus::Ok,
90 _ => ExitStatus::Usage,
91 };
92 if status == ExitStatus::Ok {
93 let _ = err.print();
94 CliError::new(String::new(), status)
95 } else {
96 CliError::new(err.to_string(), status)
97 }
98 }
99}
100
101impl From<std::io::Error> for CliError {
102 fn from(err: std::io::Error) -> Self {
103 CliError::new(err.to_string(), ExitStatus::Io)
104 }
105}
106
107impl fmt::Display for CliError {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(f, "{}", self.message)
110 }
111}
112
113impl std::error::Error for CliError {}