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("the shell keeps at most {0} file descriptors above standard error")]
334 TooManyKeptDescriptors(usize),
335
336 #[error("function name '{}' shadows a special built-in command", .name)]
338 FunctionNameShadowsSpecialBuiltin {
339 name: String,
341 },
342
343 #[error("no match: {0}")]
345 NoMatch(String),
346}
347
348pub trait BuiltinError: std::error::Error + ConvertibleToExitCode + Send + Sync {
350 fn as_io_error(&self) -> Option<&std::io::Error> {
355 None
356 }
357}
358
359impl BuiltinError for Error {
360 fn as_io_error(&self) -> Option<&std::io::Error> {
361 self.as_io_error()
362 }
363}
364
365pub trait ConvertibleToExitCode {
367 fn as_exit_code(&self) -> results::ExecutionExitCode;
369}
370
371impl<T> ConvertibleToExitCode for T
372where
373 results::ExecutionExitCode: for<'a> From<&'a T>,
374{
375 fn as_exit_code(&self) -> results::ExecutionExitCode {
376 self.into()
377 }
378}
379
380impl From<&ErrorKind> for results::ExecutionExitCode {
381 fn from(value: &ErrorKind) -> Self {
382 match value {
383 ErrorKind::CommandNotFound(..) => Self::NotFound,
384 ErrorKind::Unimplemented(..) | ErrorKind::UnimplementedAndTracked(..) => {
385 Self::Unimplemented
386 }
387 ErrorKind::ParseError(..) => Self::InvalidUsage,
388 ErrorKind::FunctionParseError(..) => Self::InvalidUsage,
389 ErrorKind::TestCommandParseError(..) => Self::InvalidUsage,
390 ErrorKind::FailedToExecuteCommand(..) => Self::CannotExecute,
391 ErrorKind::FunctionNameShadowsSpecialBuiltin { .. } => Self::InvalidUsage,
392 ErrorKind::IoError(io_err) => io_err.into(),
393 ErrorKind::BuiltinError(inner, ..) => inner.as_exit_code(),
394 _ => Self::GeneralError,
395 }
396 }
397}
398
399impl From<&std::io::Error> for results::ExecutionExitCode {
400 fn from(io_err: &std::io::Error) -> Self {
401 if io_err.kind() == std::io::ErrorKind::BrokenPipe {
402 Self::BrokenPipe
403 } else {
404 Self::GeneralError
405 }
406 }
407}
408
409impl From<&Error> for results::ExecutionExitCode {
410 fn from(error: &Error) -> Self {
411 Self::from(&*error.kind)
412 }
413}
414
415impl<T> From<T> for Error
416where
417 ErrorKind: From<T>,
418{
419 fn from(convertible_to_kind: T) -> Self {
420 Self {
421 kind: Box::new(convertible_to_kind.into()),
422 fatal: false,
423 }
424 }
425}
426
427impl Error {
428 #[must_use]
430 pub const fn into_fatal(mut self) -> Self {
431 self.fatal = true;
432 self
433 }
434
435 pub const fn is_fatal(&self) -> bool {
437 self.fatal
438 }
439
440 pub fn kind(&self) -> &ErrorKind {
442 &self.kind
443 }
444
445 pub fn as_io_error(&self) -> Option<&std::io::Error> {
447 match &*self.kind {
448 ErrorKind::IoError(io_err) => Some(io_err),
449 ErrorKind::BuiltinError(inner, _) => inner.as_io_error(),
450 _ => None,
451 }
452 }
453
454 pub fn to_control_flow(
463 &self,
464 shell: &Shell<impl extensions::ShellExtensions>,
465 ) -> results::ExecutionControlFlow {
466 if self.is_fatal() && !shell.options().interactive {
467 results::ExecutionControlFlow::ExitShell
468 } else {
469 results::ExecutionControlFlow::Normal
470 }
471 }
472
473 pub fn into_result(
479 self,
480 shell: &Shell<impl extensions::ShellExtensions>,
481 ) -> results::ExecutionResult {
482 let next_control_flow = self.to_control_flow(shell);
483 let exit_code = results::ExecutionExitCode::from(&self);
484
485 results::ExecutionResult {
486 next_control_flow,
487 exit_code,
488 }
489 }
490}
491
492pub fn unimp<T>(msg: &'static str) -> Result<T, Error> {
502 Err(ErrorKind::Unimplemented(msg).into())
503}
504
505pub fn unimp_with_issue<T>(msg: &'static str, project_issue_id: u32) -> Result<T, Error> {
518 Err(ErrorKind::UnimplementedAndTracked(msg, project_issue_id).into())
519}