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("a shell snapshot is taken or restored only between commands")]
106 NotBetweenCommands,
107
108 #[error("environment scope required for new variable is not available")]
110 MissingScopeForNewVariable,
111
112 #[error("unexpected environment scope type: expected '{expected}', found '{actual}'")]
114 UnexpectedScopeType {
115 expected: crate::env::EnvironmentScope,
117 actual: crate::env::EnvironmentScope,
119 },
120
121 #[error("not a directory: {0}")]
123 NotADirectory(PathBuf),
124
125 #[error("path is a directory")]
127 IsADirectory,
128
129 #[error("variable is not an array")]
131 NotArray,
132
133 #[error("no current user")]
135 NoCurrentUser,
136
137 #[error("invalid redirection target")]
139 InvalidRedirection,
140
141 #[error("failed to redirect to {0}: {1}")]
143 RedirectionFailure(String, String),
144
145 #[error("arithmetic evaluation error: {0}")]
147 EvalError(#[from] crate::arithmetic::EvalError),
148
149 #[error("failed to parse '{s}' as a {int_type_name}, base-{radix} integer: {inner}")]
151 IntParseError {
152 s: String,
154 int_type_name: &'static str,
156 radix: u32,
158 inner: std::num::ParseIntError,
160 },
161
162 #[error("integer conversion error")]
164 TryIntParseError(#[from] std::num::TryFromIntError),
165
166 #[error("failed to decode utf-8")]
168 FromUtf8Error(#[from] std::string::FromUtf8Error),
169
170 #[error("failed to decode utf-8")]
172 Utf8Error(#[from] std::str::Utf8Error),
173
174 #[error("cannot mutate readonly variable")]
176 ReadonlyVariable,
177
178 #[error("invalid pattern: '{0}'")]
180 InvalidPattern(String),
181
182 #[error("regex error: {0}")]
184 RegexError(#[from] fancy_regex::Error),
185
186 #[error("invalid regex: {0}; expression: '{1}'")]
188 InvalidRegexError(fancy_regex::Error, String),
189
190 #[error("i/o error: {0}")]
192 IoError(#[from] std::io::Error),
193
194 #[error("bad substitution: {0}")]
196 BadSubstitution(String),
197
198 #[error("failed to create child process")]
200 ChildCreationFailure,
201
202 #[error(transparent)]
204 FormattingError(#[from] std::fmt::Error),
205
206 #[error("{1}: {0}")]
208 ParseError(crate::parser::ParseError, crate::SourceInfo),
209
210 #[error("{0}: {1}")]
212 FunctionParseError(String, crate::parser::ParseError),
213
214 #[error(transparent)]
216 WordParseError(#[from] crate::parser::WordParseError),
217
218 #[error("invalid test command")]
220 TestCommandParseError(#[from] crate::parser::TestCommandParseError),
221
222 #[error(transparent)]
224 BindingParseError(#[from] crate::parser::BindingParseError),
225
226 #[error("task error")]
228 TaskError(#[source] Box<dyn std::error::Error + Send + Sync>),
229
230 #[error("{0}: invalid signal specification")]
232 InvalidSignal(String),
233
234 #[error("platform error: {0}")]
236 PlatformError(#[from] sys::PlatformError),
237
238 #[error("invalid umask value")]
240 InvalidUmask,
241
242 #[error("cannot read from {0}")]
244 OpenFileNotReadable(&'static str),
245
246 #[error("cannot write to {0}")]
248 OpenFileNotWritable(&'static str),
249
250 #[error("bad file descriptor: {0}")]
252 BadFileDescriptor(ShellFd),
253
254 #[error("printf failure: {0}")]
256 PrintfFailure(i32),
257
258 #[error("printf: {0}")]
260 PrintfInvalidUsage(String),
261
262 #[error("interrupted")]
264 Interrupted,
265
266 #[error("maximum function call depth exceeded")]
268 MaxFunctionCallDepthExceeded,
269
270 #[error("system time error: {0}")]
272 TimeError(#[from] std::time::SystemTimeError),
273
274 #[error("array index out of range: {0}")]
276 ArrayIndexOutOfRange(String),
277
278 #[error("unhandled key code: {0:?}")]
280 UnhandledKeyCode(Vec<u8>),
281
282 #[error("{1}: {0}")]
284 BuiltinError(Box<dyn BuiltinError>, String),
285
286 #[error("operation not supported on this platform: {0}")]
288 NotSupportedOnThisPlatform(&'static str),
289
290 #[error("command history is not enabled in this shell")]
292 HistoryNotEnabled,
293
294 #[error("expanding unset variable: {0}")]
296 ExpandingUnsetVariable(String),
297
298 #[error("internal shell error: {0}")]
300 InternalError(String),
301
302 #[error("completion position {0} isn't a char boundary in the input line")]
305 InvalidCompletionPosition(usize),
306
307 #[error("operation requires an interactive session")]
309 NotInInteractiveSession,
310
311 #[error("operation requires command-string mode")]
313 NotExecutingCommandString,
314
315 #[error("too much data")]
317 TooMuchData,
318
319 #[error("cannot convert open file to native file descriptor")]
321 CannotConvertToNativeFd,
322
323 #[error("history file is too large to import")]
325 HistoryFileTooLargeToImport,
326
327 #[error("too many open files")]
329 TooManyOpenFiles,
330
331 #[error("function name '{}' shadows a special built-in command", .name)]
333 FunctionNameShadowsSpecialBuiltin {
334 name: String,
336 },
337
338 #[error("no match: {0}")]
340 NoMatch(String),
341}
342
343pub trait BuiltinError: std::error::Error + ConvertibleToExitCode + Send + Sync {
345 fn as_io_error(&self) -> Option<&std::io::Error> {
350 None
351 }
352}
353
354impl BuiltinError for Error {
355 fn as_io_error(&self) -> Option<&std::io::Error> {
356 self.as_io_error()
357 }
358}
359
360pub trait ConvertibleToExitCode {
362 fn as_exit_code(&self) -> results::ExecutionExitCode;
364}
365
366impl<T> ConvertibleToExitCode for T
367where
368 results::ExecutionExitCode: for<'a> From<&'a T>,
369{
370 fn as_exit_code(&self) -> results::ExecutionExitCode {
371 self.into()
372 }
373}
374
375impl From<&ErrorKind> for results::ExecutionExitCode {
376 fn from(value: &ErrorKind) -> Self {
377 match value {
378 ErrorKind::CommandNotFound(..) => Self::NotFound,
379 ErrorKind::Unimplemented(..) | ErrorKind::UnimplementedAndTracked(..) => {
380 Self::Unimplemented
381 }
382 ErrorKind::ParseError(..) => Self::InvalidUsage,
383 ErrorKind::FunctionParseError(..) => Self::InvalidUsage,
384 ErrorKind::TestCommandParseError(..) => Self::InvalidUsage,
385 ErrorKind::FailedToExecuteCommand(..) => Self::CannotExecute,
386 ErrorKind::FunctionNameShadowsSpecialBuiltin { .. } => Self::InvalidUsage,
387 ErrorKind::IoError(io_err) => io_err.into(),
388 ErrorKind::BuiltinError(inner, ..) => inner.as_exit_code(),
389 _ => Self::GeneralError,
390 }
391 }
392}
393
394impl From<&std::io::Error> for results::ExecutionExitCode {
395 fn from(io_err: &std::io::Error) -> Self {
396 if io_err.kind() == std::io::ErrorKind::BrokenPipe {
397 Self::BrokenPipe
398 } else {
399 Self::GeneralError
400 }
401 }
402}
403
404impl From<&Error> for results::ExecutionExitCode {
405 fn from(error: &Error) -> Self {
406 Self::from(&*error.kind)
407 }
408}
409
410impl<T> From<T> for Error
411where
412 ErrorKind: From<T>,
413{
414 fn from(convertible_to_kind: T) -> Self {
415 Self {
416 kind: Box::new(convertible_to_kind.into()),
417 fatal: false,
418 }
419 }
420}
421
422impl Error {
423 #[must_use]
425 pub const fn into_fatal(mut self) -> Self {
426 self.fatal = true;
427 self
428 }
429
430 pub const fn is_fatal(&self) -> bool {
432 self.fatal
433 }
434
435 pub fn kind(&self) -> &ErrorKind {
437 &self.kind
438 }
439
440 pub fn as_io_error(&self) -> Option<&std::io::Error> {
442 match &*self.kind {
443 ErrorKind::IoError(io_err) => Some(io_err),
444 ErrorKind::BuiltinError(inner, _) => inner.as_io_error(),
445 _ => None,
446 }
447 }
448
449 pub fn to_control_flow(
458 &self,
459 shell: &Shell<impl extensions::ShellExtensions>,
460 ) -> results::ExecutionControlFlow {
461 if self.is_fatal() && !shell.options().interactive {
462 results::ExecutionControlFlow::ExitShell
463 } else {
464 results::ExecutionControlFlow::Normal
465 }
466 }
467
468 pub fn into_result(
474 self,
475 shell: &Shell<impl extensions::ShellExtensions>,
476 ) -> results::ExecutionResult {
477 let next_control_flow = self.to_control_flow(shell);
478 let exit_code = results::ExecutionExitCode::from(&self);
479
480 results::ExecutionResult {
481 next_control_flow,
482 exit_code,
483 }
484 }
485}
486
487pub fn unimp<T>(msg: &'static str) -> Result<T, Error> {
497 Err(ErrorKind::Unimplemented(msg).into())
498}
499
500pub fn unimp_with_issue<T>(msg: &'static str, project_issue_id: u32) -> Result<T, Error> {
513 Err(ErrorKind::UnimplementedAndTracked(msg, project_issue_id).into())
514}