1use std::path::PathBuf;
4
5use crate::{Shell, ShellFd, extensions, results, sys};
6
7#[derive(thiserror::Error, Debug)]
10#[error("{kind}")]
11pub struct Error {
12 #[source]
14 kind: Box<ErrorKind>,
15
16 fatal: bool,
19}
20
21#[derive(thiserror::Error, Debug)]
23pub enum ErrorKind {
24 #[error("cannot expand tilde expression with HOME not set")]
26 TildeWithoutValidHome,
27
28 #[error("cannot assign list to array member")]
30 AssigningListToArrayMember,
31
32 #[error("cannot convert associative array to indexed array")]
34 ConvertingAssociativeArrayToIndexedArray,
35
36 #[error("cannot convert indexed array to associative array")]
38 ConvertingIndexedArrayToAssociativeArray,
39
40 #[error("failed to source file: {0}")]
42 FailedSourcingFile(PathBuf, #[source] std::io::Error),
43
44 #[error("no such process")]
46 NoSuchProcess,
47
48 #[error("operation not permitted")]
50 PermissionDenied,
51
52 #[error("failed to send signal to process")]
54 FailedToSendSignal,
55
56 #[error("cannot assign in this way")]
58 CannotAssignToSpecialParameter,
59
60 #[error("expansion error: {0}")]
62 CheckedExpansionError(String),
63
64 #[error("function not found: {0}")]
66 FunctionNotFound(String),
67
68 #[error("command not found: {0}")]
70 CommandNotFound(String),
71
72 #[error("not a shell builtin: {0}")]
74 BuiltinNotFound(String),
75
76 #[error("working directory does not exist: {0}")]
78 WorkingDirMissing(PathBuf),
79
80 #[error("failed to execute command '{0}': {1}")]
82 FailedToExecuteCommand(String, #[source] std::io::Error),
83
84 #[error("history item not found")]
86 HistoryItemNotFound,
87
88 #[error("not yet implemented: {0}")]
90 Unimplemented(&'static str),
91
92 #[error(
95 "not yet implemented: {0}; see https://github.com/victor-software-house/shrub/issues/{1}"
96 )]
97 UnimplementedAndTracked(&'static str, u32),
98
99 #[error("missing environment scope")]
101 MissingScope,
102
103 #[error("environment scope required for new variable is not available")]
105 MissingScopeForNewVariable,
106
107 #[error("unexpected environment scope type: expected '{expected}', found '{actual}'")]
109 UnexpectedScopeType {
110 expected: crate::env::EnvironmentScope,
112 actual: crate::env::EnvironmentScope,
114 },
115
116 #[error("not a directory: {0}")]
118 NotADirectory(PathBuf),
119
120 #[error("path is a directory")]
122 IsADirectory,
123
124 #[error("variable is not an array")]
126 NotArray,
127
128 #[error("no current user")]
130 NoCurrentUser,
131
132 #[error("invalid redirection target")]
134 InvalidRedirection,
135
136 #[error("failed to redirect to {0}: {1}")]
138 RedirectionFailure(String, String),
139
140 #[error("arithmetic evaluation error: {0}")]
142 EvalError(#[from] crate::arithmetic::EvalError),
143
144 #[error("failed to parse '{s}' as a {int_type_name}, base-{radix} integer: {inner}")]
146 IntParseError {
147 s: String,
149 int_type_name: &'static str,
151 radix: u32,
153 inner: std::num::ParseIntError,
155 },
156
157 #[error("integer conversion error")]
159 TryIntParseError(#[from] std::num::TryFromIntError),
160
161 #[error("failed to decode utf-8")]
163 FromUtf8Error(#[from] std::string::FromUtf8Error),
164
165 #[error("failed to decode utf-8")]
167 Utf8Error(#[from] std::str::Utf8Error),
168
169 #[error("cannot mutate readonly variable")]
171 ReadonlyVariable,
172
173 #[error("invalid pattern: '{0}'")]
175 InvalidPattern(String),
176
177 #[error("regex error: {0}")]
179 RegexError(#[from] fancy_regex::Error),
180
181 #[error("invalid regex: {0}; expression: '{1}'")]
183 InvalidRegexError(fancy_regex::Error, String),
184
185 #[error("i/o error: {0}")]
187 IoError(#[from] std::io::Error),
188
189 #[error("bad substitution: {0}")]
191 BadSubstitution(String),
192
193 #[error("failed to create child process")]
195 ChildCreationFailure,
196
197 #[error(transparent)]
199 FormattingError(#[from] std::fmt::Error),
200
201 #[error("{1}: {0}")]
203 ParseError(crate::parser::ParseError, crate::SourceInfo),
204
205 #[error("{0}: {1}")]
207 FunctionParseError(String, crate::parser::ParseError),
208
209 #[error(transparent)]
211 WordParseError(#[from] crate::parser::WordParseError),
212
213 #[error("invalid test command")]
215 TestCommandParseError(#[from] crate::parser::TestCommandParseError),
216
217 #[error(transparent)]
219 BindingParseError(#[from] crate::parser::BindingParseError),
220
221 #[error("task error")]
223 TaskError(#[source] Box<dyn std::error::Error + Send + Sync>),
224
225 #[error("{0}: invalid signal specification")]
227 InvalidSignal(String),
228
229 #[error("platform error: {0}")]
231 PlatformError(#[from] sys::PlatformError),
232
233 #[error("invalid umask value")]
235 InvalidUmask,
236
237 #[error("cannot read from {0}")]
239 OpenFileNotReadable(&'static str),
240
241 #[error("cannot write to {0}")]
243 OpenFileNotWritable(&'static str),
244
245 #[error("bad file descriptor: {0}")]
247 BadFileDescriptor(ShellFd),
248
249 #[error("printf failure: {0}")]
251 PrintfFailure(i32),
252
253 #[error("printf: {0}")]
255 PrintfInvalidUsage(String),
256
257 #[error("interrupted")]
259 Interrupted,
260
261 #[error("maximum function call depth exceeded")]
263 MaxFunctionCallDepthExceeded,
264
265 #[error("system time error: {0}")]
267 TimeError(#[from] std::time::SystemTimeError),
268
269 #[error("array index out of range: {0}")]
271 ArrayIndexOutOfRange(String),
272
273 #[error("unhandled key code: {0:?}")]
275 UnhandledKeyCode(Vec<u8>),
276
277 #[error("{1}: {0}")]
279 BuiltinError(Box<dyn BuiltinError>, String),
280
281 #[error("operation not supported on this platform: {0}")]
283 NotSupportedOnThisPlatform(&'static str),
284
285 #[error("command history is not enabled in this shell")]
287 HistoryNotEnabled,
288
289 #[error("expanding unset variable: {0}")]
291 ExpandingUnsetVariable(String),
292
293 #[error("internal shell error: {0}")]
295 InternalError(String),
296
297 #[error("completion position {0} isn't a char boundary in the input line")]
300 InvalidCompletionPosition(usize),
301
302 #[error("operation requires an interactive session")]
304 NotInInteractiveSession,
305
306 #[error("operation requires command-string mode")]
308 NotExecutingCommandString,
309
310 #[error("too much data")]
312 TooMuchData,
313
314 #[error("cannot convert open file to native file descriptor")]
316 CannotConvertToNativeFd,
317
318 #[error("history file is too large to import")]
320 HistoryFileTooLargeToImport,
321
322 #[error("too many open files")]
324 TooManyOpenFiles,
325
326 #[error("function name '{}' shadows a special built-in command", .name)]
328 FunctionNameShadowsSpecialBuiltin {
329 name: String,
331 },
332
333 #[error("no match: {0}")]
335 NoMatch(String),
336}
337
338pub trait BuiltinError: std::error::Error + ConvertibleToExitCode + Send + Sync {
340 fn as_io_error(&self) -> Option<&std::io::Error> {
345 None
346 }
347}
348
349impl BuiltinError for Error {
350 fn as_io_error(&self) -> Option<&std::io::Error> {
351 self.as_io_error()
352 }
353}
354
355pub trait ConvertibleToExitCode {
357 fn as_exit_code(&self) -> results::ExecutionExitCode;
359}
360
361impl<T> ConvertibleToExitCode for T
362where
363 results::ExecutionExitCode: for<'a> From<&'a T>,
364{
365 fn as_exit_code(&self) -> results::ExecutionExitCode {
366 self.into()
367 }
368}
369
370impl From<&ErrorKind> for results::ExecutionExitCode {
371 fn from(value: &ErrorKind) -> Self {
372 match value {
373 ErrorKind::CommandNotFound(..) => Self::NotFound,
374 ErrorKind::Unimplemented(..) | ErrorKind::UnimplementedAndTracked(..) => {
375 Self::Unimplemented
376 }
377 ErrorKind::ParseError(..) => Self::InvalidUsage,
378 ErrorKind::FunctionParseError(..) => Self::InvalidUsage,
379 ErrorKind::TestCommandParseError(..) => Self::InvalidUsage,
380 ErrorKind::FailedToExecuteCommand(..) => Self::CannotExecute,
381 ErrorKind::FunctionNameShadowsSpecialBuiltin { .. } => Self::InvalidUsage,
382 ErrorKind::IoError(io_err) => io_err.into(),
383 ErrorKind::BuiltinError(inner, ..) => inner.as_exit_code(),
384 _ => Self::GeneralError,
385 }
386 }
387}
388
389impl From<&std::io::Error> for results::ExecutionExitCode {
390 fn from(io_err: &std::io::Error) -> Self {
391 if io_err.kind() == std::io::ErrorKind::BrokenPipe {
392 Self::BrokenPipe
393 } else {
394 Self::GeneralError
395 }
396 }
397}
398
399impl From<&Error> for results::ExecutionExitCode {
400 fn from(error: &Error) -> Self {
401 Self::from(&*error.kind)
402 }
403}
404
405impl<T> From<T> for Error
406where
407 ErrorKind: From<T>,
408{
409 fn from(convertible_to_kind: T) -> Self {
410 Self {
411 kind: Box::new(convertible_to_kind.into()),
412 fatal: false,
413 }
414 }
415}
416
417impl Error {
418 #[must_use]
420 pub const fn into_fatal(mut self) -> Self {
421 self.fatal = true;
422 self
423 }
424
425 pub const fn is_fatal(&self) -> bool {
427 self.fatal
428 }
429
430 pub fn kind(&self) -> &ErrorKind {
432 &self.kind
433 }
434
435 pub fn as_io_error(&self) -> Option<&std::io::Error> {
437 match &*self.kind {
438 ErrorKind::IoError(io_err) => Some(io_err),
439 ErrorKind::BuiltinError(inner, _) => inner.as_io_error(),
440 _ => None,
441 }
442 }
443
444 pub fn to_control_flow(
453 &self,
454 shell: &Shell<impl extensions::ShellExtensions>,
455 ) -> results::ExecutionControlFlow {
456 if self.is_fatal() && !shell.options().interactive {
457 results::ExecutionControlFlow::ExitShell
458 } else {
459 results::ExecutionControlFlow::Normal
460 }
461 }
462
463 pub fn into_result(
469 self,
470 shell: &Shell<impl extensions::ShellExtensions>,
471 ) -> results::ExecutionResult {
472 let next_control_flow = self.to_control_flow(shell);
473 let exit_code = results::ExecutionExitCode::from(&self);
474
475 results::ExecutionResult {
476 next_control_flow,
477 exit_code,
478 }
479 }
480}
481
482pub fn unimp<T>(msg: &'static str) -> Result<T, Error> {
492 Err(ErrorKind::Unimplemented(msg).into())
493}
494
495pub fn unimp_with_issue<T>(msg: &'static str, project_issue_id: u32) -> Result<T, Error> {
508 Err(ErrorKind::UnimplementedAndTracked(msg, project_issue_id).into())
509}