garden/
errors.rs

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    /// ExitStatus is used to exit without printing an error message.
25    #[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    /// ExitStatus is used to exit without printing an error message.
98    #[error("{command} returned exit status {status}")]
99    ExitStatus { command: String, status: i32 },
100}
101
102// /usr/include/sysexits.h
103pub 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, // Explicit exit code
123            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
145/// Convert an i32 into a GardenError.
146pub fn error_from_exit_status(exit_status: i32) -> GardenError {
147    GardenError::ExitStatus(exit_status)
148}
149
150/// Convert a Result<(), i32> into a Result<(), anyhow::Error>
151pub fn error_from_exit_status_result(result: Result<(), i32>) -> Result<()> {
152    result.map_err(|status| error_from_exit_status(status).into())
153}
154
155/// Convert an i32 exit status to Result<(), GardenError>.
156pub 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
163/// Convert an i32 exit status to an anyhow::Result<()>
164pub fn exit_status_into_result(exit_status: i32) -> Result<()> {
165    result_from_exit_status(exit_status).map_err(|err| err.into())
166}
167
168//r Transform an anyhow::Error into an exit code when an error occurs.
169pub fn exit_status_from_error(err: anyhow::Error) -> i32 {
170    match err.downcast::<GardenError>() {
171        Ok(garden_err) => {
172            match garden_err {
173                // ExitStatus exits without printing a message.
174                GardenError::ExitStatus(status) => status,
175                // Other GardenError variants print a message before exiting.
176                _ => {
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}