1use anyhow::Result;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum GardenError {
6 #[error("assertion error: {0}")]
7 AssertionError(String),
8
9 #[error("configuration error: {0}")]
10 ConfigurationError(String),
11
12 #[error("{path:?}: unable to create configuration: {err}")]
13 CreateConfigurationError {
14 path: std::path::PathBuf,
15 err: std::io::Error,
16 },
17
18 #[error("invalid configuration: empty document: {path:?}")]
19 EmptyConfiguration { path: std::path::PathBuf },
20
21 #[error("tree query found no trees: '{0}'")]
22 EmptyTreeQueryResult(String),
23
24 #[error("exit status {0}")]
26 ExitStatus(i32),
27
28 #[error("{0}")]
29 FileExists(String),
30
31 #[error("file not found")]
32 FileNotFound,
33
34 #[error("unable to find '{garden}': No garden exists with that name")]
35 GardenNotFound { garden: String },
36
37 #[error("'{garden}' is not a valid garden glob pattern")]
38 GardenPatternError { garden: String },
39
40 #[error("{0}")]
41 IOError(String),
42
43 #[error("invalid configuration: {msg}")]
44 InvalidConfiguration { msg: String },
45
46 #[error("invalid argument: '{tree}' is not part of the '{garden}' garden")]
47 InvalidGardenArgument { tree: String, garden: String },
48
49 #[error("{0}")]
50 OSError(String),
51
52 #[error("unable to read {path:?}\nerror: {err}")]
53 ReadConfig {
54 err: yaml_rust::ScanError,
55 path: String,
56 },
57
58 #[error("unable to read {path:?}: {err}")]
59 ReadFile {
60 path: std::path::PathBuf,
61 err: std::io::Error,
62 },
63
64 #[error("unable to sync configuration: {path:?}: {err:?}")]
65 SyncConfigurationError {
66 path: std::path::PathBuf,
67 err: std::io::Error,
68 },
69
70 #[error("unable to find '{tree}': No tree exists with that name")]
71 TreeNotFound { tree: String },
72
73 #[error("invalid arguments: {0}")]
74 Usage(String),
75
76 #[error("error creating {tree:?}: 'git checkout' returned exit status {status:?}")]
77 WorktreeGitCheckoutError { tree: String, status: i32 },
78
79 #[error("unable to find worktree {worktree:?} for {tree:?}")]
80 WorktreeNotFound { worktree: String, tree: String },
81
82 #[error("error creating worktree parent {worktree:?} for {tree:?}")]
83 WorktreeParentCreationError { worktree: String, tree: String },
84
85 #[error("unable to plant {tree:?}: worktree parent {parent:?} has not been planted")]
86 WorktreeParentNotPlantedError {
87 parent: std::path::PathBuf,
88 tree: String,
89 },
90
91 #[error("unable to write configuration: {path:?}")]
92 WriteConfigurationError { path: std::path::PathBuf },
93}
94
95#[derive(Error, Debug)]
96pub enum CommandError {
97 #[error("{command} returned exit status {status}")]
99 ExitStatus { command: String, status: i32 },
100}
101
102pub const EX_OK: i32 = 0;
104pub const EX_ERROR: i32 = 1;
105pub const EX_USAGE: i32 = 64;
106pub const EX_DATAERR: i32 = 65;
107pub const EX_UNAVAILABLE: i32 = 69;
108pub const EX_SOFTWARE: i32 = 70;
109pub const EX_OSERR: i32 = 71;
110pub const EX_CANTCREAT: i32 = 73;
111pub const EX_IOERR: i32 = 74;
112pub const EX_CONFIG: i32 = 78;
113
114impl std::convert::From<GardenError> for i32 {
115 fn from(garden_err: GardenError) -> Self {
116 match garden_err {
117 GardenError::AssertionError(_) => EX_SOFTWARE,
118 GardenError::ConfigurationError(_) => EX_CONFIG,
119 GardenError::CreateConfigurationError { .. } => EX_CANTCREAT,
120 GardenError::EmptyConfiguration { .. } => EX_CONFIG,
121 GardenError::EmptyTreeQueryResult(_) => EX_DATAERR,
122 GardenError::ExitStatus(status) => status, GardenError::FileExists(_) => EX_CANTCREAT,
124 GardenError::FileNotFound => EX_IOERR,
125 GardenError::GardenNotFound { .. } => EX_USAGE,
126 GardenError::GardenPatternError { .. } => EX_DATAERR,
127 GardenError::IOError(_) => EX_IOERR,
128 GardenError::InvalidConfiguration { .. } => EX_CONFIG,
129 GardenError::InvalidGardenArgument { .. } => EX_USAGE,
130 GardenError::OSError(_) => EX_OSERR,
131 GardenError::ReadConfig { .. } => EX_DATAERR,
132 GardenError::ReadFile { .. } => EX_IOERR,
133 GardenError::SyncConfigurationError { .. } => EX_IOERR,
134 GardenError::TreeNotFound { .. } => EX_USAGE,
135 GardenError::Usage(_) => EX_USAGE,
136 GardenError::WorktreeGitCheckoutError { .. } => EX_CANTCREAT,
137 GardenError::WorktreeParentCreationError { .. } => EX_CANTCREAT,
138 GardenError::WorktreeParentNotPlantedError { .. } => EX_CONFIG,
139 GardenError::WorktreeNotFound { .. } => EX_CONFIG,
140 GardenError::WriteConfigurationError { .. } => EX_CANTCREAT,
141 }
142 }
143}
144
145pub fn error_from_exit_status(exit_status: i32) -> GardenError {
147 GardenError::ExitStatus(exit_status)
148}
149
150pub fn error_from_exit_status_result(result: Result<(), i32>) -> Result<()> {
152 result.map_err(|status| error_from_exit_status(status).into())
153}
154
155pub fn result_from_exit_status(exit_status: i32) -> Result<(), GardenError> {
157 match exit_status {
158 EX_OK => Ok(()),
159 _ => Err(error_from_exit_status(exit_status)),
160 }
161}
162
163pub fn exit_status_into_result(exit_status: i32) -> Result<()> {
165 result_from_exit_status(exit_status).map_err(|err| err.into())
166}
167
168pub fn exit_status_from_error(err: anyhow::Error) -> i32 {
170 match err.downcast::<GardenError>() {
171 Ok(garden_err) => {
172 match garden_err {
173 GardenError::ExitStatus(status) => status,
175 _ => {
177 eprintln!("error: {garden_err:#}");
178 garden_err.into()
179 }
180 }
181 }
182 Err(other_err) => {
183 eprintln!("error: {other_err:#}");
184 1
185 }
186 }
187}