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: 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("failed to send signal to process")]
46 FailedToSendSignal,
47
48 #[error("cannot assign in this way")]
50 CannotAssignToSpecialParameter,
51
52 #[error("expansion error: {0}")]
54 CheckedExpansionError(String),
55
56 #[error("function not found: {0}")]
58 FunctionNotFound(String),
59
60 #[error("command not found: {0}")]
62 CommandNotFound(String),
63
64 #[error("not a shell builtin: {0}")]
66 BuiltinNotFound(String),
67
68 #[error("working directory does not exist: {0}")]
70 WorkingDirMissing(PathBuf),
71
72 #[error("failed to execute command '{0}': {1}")]
74 FailedToExecuteCommand(String, #[source] std::io::Error),
75
76 #[error("history item not found")]
78 HistoryItemNotFound,
79
80 #[error("not yet implemented: {0}")]
82 Unimplemented(&'static str),
83
84 #[error("not yet implemented: {0}; see https://github.com/reubeno/brush/issues/{1}")]
87 UnimplementedAndTracked(&'static str, u32),
88
89 #[error("missing environment scope")]
91 MissingScope,
92
93 #[error("environment scope required for new variable is not available")]
95 MissingScopeForNewVariable,
96
97 #[error("unexpected environment scope type: expected '{expected}', found '{actual}'")]
99 UnexpectedScopeType {
100 expected: crate::env::EnvironmentScope,
102 actual: crate::env::EnvironmentScope,
104 },
105
106 #[error("not a directory: {0}")]
108 NotADirectory(PathBuf),
109
110 #[error("path is a directory")]
112 IsADirectory,
113
114 #[error("variable is not an array")]
116 NotArray,
117
118 #[error("no current user")]
120 NoCurrentUser,
121
122 #[error("invalid redirection target")]
124 InvalidRedirection,
125
126 #[error("failed to redirect to {0}: {1}")]
128 RedirectionFailure(String, String),
129
130 #[error("arithmetic evaluation error: {0}")]
132 EvalError(#[from] crate::arithmetic::EvalError),
133
134 #[error("failed to parse '{s}' as a {int_type_name}, base-{radix} integer: {inner}")]
136 IntParseError {
137 s: String,
139 int_type_name: &'static str,
141 radix: u32,
143 inner: std::num::ParseIntError,
145 },
146
147 #[error("integer conversion error")]
149 TryIntParseError(#[from] std::num::TryFromIntError),
150
151 #[error("failed to decode utf-8")]
153 FromUtf8Error(#[from] std::string::FromUtf8Error),
154
155 #[error("failed to decode utf-8")]
157 Utf8Error(#[from] std::str::Utf8Error),
158
159 #[error("cannot mutate readonly variable")]
161 ReadonlyVariable,
162
163 #[error("invalid pattern: '{0}'")]
165 InvalidPattern(String),
166
167 #[error("regex error: {0}")]
169 RegexError(#[from] fancy_regex::Error),
170
171 #[error("invalid regex: {0}; expression: '{1}'")]
173 InvalidRegexError(fancy_regex::Error, String),
174
175 #[error("i/o error: {0}")]
177 IoError(#[from] std::io::Error),
178
179 #[error("bad substitution: {0}")]
181 BadSubstitution(String),
182
183 #[error("failed to create child process")]
185 ChildCreationFailure,
186
187 #[error(transparent)]
189 FormattingError(#[from] std::fmt::Error),
190
191 #[error("{1}: {0}")]
193 ParseError(crate::parser::ParseError, crate::SourceInfo),
194
195 #[error("{0}: {1}")]
197 FunctionParseError(String, crate::parser::ParseError),
198
199 #[error(transparent)]
201 WordParseError(#[from] crate::parser::WordParseError),
202
203 #[error("invalid test command")]
205 TestCommandParseError(#[from] crate::parser::TestCommandParseError),
206
207 #[error(transparent)]
209 BindingParseError(#[from] crate::parser::BindingParseError),
210
211 #[error("threading error")]
213 ThreadingError(#[from] tokio::task::JoinError),
214
215 #[error("{0}: invalid signal specification")]
217 InvalidSignal(String),
218
219 #[error("platform error: {0}")]
221 PlatformError(#[from] sys::PlatformError),
222
223 #[error("invalid umask value")]
225 InvalidUmask,
226
227 #[error("cannot read from {0}")]
229 OpenFileNotReadable(&'static str),
230
231 #[error("cannot write to {0}")]
233 OpenFileNotWritable(&'static str),
234
235 #[error("bad file descriptor: {0}")]
237 BadFileDescriptor(ShellFd),
238
239 #[error("printf failure: {0}")]
241 PrintfFailure(i32),
242
243 #[error("printf: {0}")]
245 PrintfInvalidUsage(String),
246
247 #[error("interrupted")]
249 Interrupted,
250
251 #[error("maximum function call depth exceeded")]
253 MaxFunctionCallDepthExceeded,
254
255 #[error("system time error: {0}")]
257 TimeError(#[from] std::time::SystemTimeError),
258
259 #[error("array index out of range: {0}")]
261 ArrayIndexOutOfRange(String),
262
263 #[error("unhandled key code: {0:?}")]
265 UnhandledKeyCode(Vec<u8>),
266
267 #[error("{1}: {0}")]
269 BuiltinError(Box<dyn BuiltinError>, String),
270
271 #[error("operation not supported on this platform: {0}")]
273 NotSupportedOnThisPlatform(&'static str),
274
275 #[error("command history is not enabled in this shell")]
277 HistoryNotEnabled,
278
279 #[error("expanding unset variable: {0}")]
281 ExpandingUnsetVariable(String),
282
283 #[error("internal shell error: {0}")]
285 InternalError(String),
286
287 #[error("operation requires an interactive session")]
289 NotInInteractiveSession,
290
291 #[error("operation requires command-string mode")]
293 NotExecutingCommandString,
294
295 #[error("too much data")]
297 TooMuchData,
298
299 #[error("cannot convert open file to native file descriptor")]
301 CannotConvertToNativeFd,
302
303 #[error("history file is too large to import")]
305 HistoryFileTooLargeToImport,
306
307 #[error("too many open files")]
309 TooManyOpenFiles,
310
311 #[error("function name '{}' shadows a special built-in command", .name)]
313 FunctionNameShadowsSpecialBuiltin {
314 name: String,
316 },
317
318 #[error("no match: {0}")]
320 NoMatch(String),
321}
322
323pub trait BuiltinError: std::error::Error + ConvertibleToExitCode + Send + Sync {
325 fn as_io_error(&self) -> Option<&std::io::Error> {
330 None
331 }
332}
333
334impl BuiltinError for Error {
335 fn as_io_error(&self) -> Option<&std::io::Error> {
336 self.as_io_error()
337 }
338}
339
340pub trait ConvertibleToExitCode {
342 fn as_exit_code(&self) -> results::ExecutionExitCode;
344}
345
346impl<T> ConvertibleToExitCode for T
347where
348 results::ExecutionExitCode: for<'a> From<&'a T>,
349{
350 fn as_exit_code(&self) -> results::ExecutionExitCode {
351 self.into()
352 }
353}
354
355impl From<&ErrorKind> for results::ExecutionExitCode {
356 fn from(value: &ErrorKind) -> Self {
357 match value {
358 ErrorKind::CommandNotFound(..) => Self::NotFound,
359 ErrorKind::Unimplemented(..) | ErrorKind::UnimplementedAndTracked(..) => {
360 Self::Unimplemented
361 }
362 ErrorKind::ParseError(..) => Self::InvalidUsage,
363 ErrorKind::FunctionParseError(..) => Self::InvalidUsage,
364 ErrorKind::TestCommandParseError(..) => Self::InvalidUsage,
365 ErrorKind::FailedToExecuteCommand(..) => Self::CannotExecute,
366 ErrorKind::FunctionNameShadowsSpecialBuiltin { .. } => Self::InvalidUsage,
367 ErrorKind::IoError(io_err) => io_err.into(),
368 ErrorKind::BuiltinError(inner, ..) => inner.as_exit_code(),
369 _ => Self::GeneralError,
370 }
371 }
372}
373
374impl From<&std::io::Error> for results::ExecutionExitCode {
375 fn from(io_err: &std::io::Error) -> Self {
376 if io_err.kind() == std::io::ErrorKind::BrokenPipe {
377 Self::BrokenPipe
378 } else {
379 Self::GeneralError
380 }
381 }
382}
383
384impl From<&Error> for results::ExecutionExitCode {
385 fn from(error: &Error) -> Self {
386 Self::from(&error.kind)
387 }
388}
389
390impl<T> From<T> for Error
391where
392 ErrorKind: From<T>,
393{
394 fn from(convertible_to_kind: T) -> Self {
395 Self {
396 kind: convertible_to_kind.into(),
397 fatal: false,
398 }
399 }
400}
401
402impl Error {
403 #[must_use]
405 pub const fn into_fatal(mut self) -> Self {
406 self.fatal = true;
407 self
408 }
409
410 pub const fn is_fatal(&self) -> bool {
412 self.fatal
413 }
414
415 pub const fn kind(&self) -> &ErrorKind {
417 &self.kind
418 }
419
420 pub fn as_io_error(&self) -> Option<&std::io::Error> {
422 match &self.kind {
423 ErrorKind::IoError(io_err) => Some(io_err),
424 ErrorKind::BuiltinError(inner, _) => inner.as_io_error(),
425 _ => None,
426 }
427 }
428
429 pub fn to_control_flow(
436 &self,
437 shell: &Shell<impl extensions::ShellExtensions>,
438 ) -> results::ExecutionControlFlow {
439 if self.is_fatal() && !shell.options().interactive {
440 results::ExecutionControlFlow::ExitShell
441 } else {
442 results::ExecutionControlFlow::Normal
443 }
444 }
445
446 pub fn into_result(
452 self,
453 shell: &Shell<impl extensions::ShellExtensions>,
454 ) -> results::ExecutionResult {
455 let next_control_flow = self.to_control_flow(shell);
456 let exit_code = results::ExecutionExitCode::from(&self);
457
458 results::ExecutionResult {
459 next_control_flow,
460 exit_code,
461 }
462 }
463}
464
465pub fn unimp<T>(msg: &'static str) -> Result<T, Error> {
471 Err(ErrorKind::Unimplemented(msg).into())
472}
473
474pub fn unimp_with_issue<T>(msg: &'static str, project_issue_id: u32) -> Result<T, Error> {
481 Err(ErrorKind::UnimplementedAndTracked(msg, project_issue_id).into())
482}