From 2a86257816be939876228203269cdc132fab82d0 Mon Sep 17 00:00:00 2001
From: Xavier L'Heureux <xavier.lheureux@icloud.com>
Date: Mon, 10 Jun 2019 12:43:22 -0400
Subject: [PATCH] Add function errors and follow the error styleguide

---
 examples/fn.out                    |  4 +--
 src/lib/shell/flow_control.rs      | 25 +++++-----------
 src/lib/shell/mod.rs               | 44 +++++++++++++--------------
 src/lib/shell/pipe_exec/fork.rs    | 13 ++++----
 src/lib/shell/pipe_exec/mod.rs     | 48 ++++++++++--------------------
 src/lib/shell/pipe_exec/pipes.rs   |  2 +-
 src/lib/shell/pipe_exec/streams.rs |  4 +--
 7 files changed, 55 insertions(+), 85 deletions(-)

diff --git a/examples/fn.out b/examples/fn.out
index d593d50c..7a2e55aa 100644
--- a/examples/fn.out
+++ b/examples/fn.out
@@ -15,5 +15,5 @@ goodbye
 9
 16
 25
-ion: function argument has invalid type: expected int, found value '$num'
-ion: function argument has invalid type: expected int, found value '$num'
+ion: could not run function: argument has invalid type: expected int, found value '$num'
+ion: could not run function: argument has invalid type: expected int, found value '$num'
diff --git a/src/lib/shell/flow_control.rs b/src/lib/shell/flow_control.rs
index 69967e24..cb2f2502 100644
--- a/src/lib/shell/flow_control.rs
+++ b/src/lib/shell/flow_control.rs
@@ -6,7 +6,6 @@ use crate::{
 };
 use small;
 use smallvec::SmallVec;
-use std::fmt::{self, Display, Formatter};
 
 #[derive(Debug, PartialEq, Clone)]
 pub struct ElseIf<'a> {
@@ -344,20 +343,12 @@ pub struct Function<'a> {
     statements:  Block<'a>,
 }
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Clone, Error)]
 pub enum FunctionError {
+    #[error(display = "invalid number of arguments supplied")]
     InvalidArgumentCount,
-    InvalidArgumentType(Primitive, String),
-}
-
-impl Display for FunctionError {
-    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
-        use self::FunctionError::*;
-        match *self {
-            InvalidArgumentCount => write!(fmt, "invalid number of arguments"),
-            InvalidArgumentType(ref t, ref value) => write!(fmt, "{} is not of type {}", value, t),
-        }
-    }
+    #[error(display = "argument has invalid type: expected {}, found value '{}'", t, val)]
+    InvalidArgumentType { t: Primitive, val: String },
 }
 
 impl<'a> Function<'a> {
@@ -380,10 +371,10 @@ impl<'a> Function<'a> {
                 if let Ok(value) = value_check(shell, value.as_ref(), &type_.kind) {
                     Ok((type_.clone(), value))
                 } else {
-                    Err(FunctionError::InvalidArgumentType(
-                        type_.kind.clone(),
-                        value.as_ref().into(),
-                    ))
+                    Err(FunctionError::InvalidArgumentType {
+                        t:   type_.kind.clone(),
+                        val: value.as_ref().into(),
+                    })
                 }
             })
             .collect::<Result<SmallVec<[_; 8]>, _>>()?;
diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs
index ffd1f292..98b17e19 100644
--- a/src/lib/shell/mod.rs
+++ b/src/lib/shell/mod.rs
@@ -323,18 +323,19 @@ impl<'a> Shell<'a> {
         let command_start_time = SystemTime::now();
 
         pipeline.expand(self);
-        // Branch if -> input == shell command i.e. echo
-        let exit_status = if let Some(main) = self.builtins.get(pipeline.items[0].command()) {
+        // A string representing the command is stored here.
+        if self.opts.print_comms {
+            eprintln!("> {}", pipeline);
+        }
+
+        // Don't execute commands when the `-n` flag is passed.
+        let exit_status = if self.opts.no_exec {
+            Ok(Status::SUCCESS)
+        // Branch else if -> input == shell command i.e. echo
+        } else if let Some(main) = self.builtins.get(pipeline.items[0].command()) {
             // Run the 'main' of the command and set exit_status
             if !pipeline.requires_piping() {
-                if self.opts.print_comms {
-                    eprintln!("> {}", pipeline.to_string());
-                }
-                if self.opts.no_exec {
-                    Status::SUCCESS
-                } else {
-                    main(&pipeline.items[0].job.args, self)
-                }
+                Ok(main(&pipeline.items[0].job.args, self))
             } else {
                 self.execute_pipeline(pipeline)
             }
@@ -343,25 +344,20 @@ impl<'a> Shell<'a> {
             self.variables.get_ref(&pipeline.items[0].job.args[0]).cloned()
         {
             if !pipeline.requires_piping() {
-                match function.execute(self, &pipeline.items[0].job.args) {
-                    Ok(()) => self.previous_status,
-                    Err(FunctionError::InvalidArgumentCount) => {
-                        Status::error("ion: invalid number of function arguments supplied")
-                    }
-                    Err(FunctionError::InvalidArgumentType(expected_type, value)) => {
-                        Status::error(format!(
-                            "ion: function argument has invalid type: expected {}, found value \
-                             \'{}\'",
-                            expected_type, value
-                        ))
-                    }
-                }
+                function
+                    .execute(self, &pipeline.items[0].job.args)
+                    .map(|_| self.previous_status)
+                    .map_err(Into::into)
             } else {
                 self.execute_pipeline(pipeline)
             }
         } else {
             self.execute_pipeline(pipeline)
-        };
+        }
+        .unwrap_or_else(|err| {
+            eprintln!("{}", err);
+            Status::COULD_NOT_EXEC
+        });
 
         if let Some(ref callback) = self.on_command {
             if let Ok(elapsed_time) = command_start_time.elapsed() {
diff --git a/src/lib/shell/pipe_exec/fork.rs b/src/lib/shell/pipe_exec/fork.rs
index d6f44d8d..4cd2f48f 100644
--- a/src/lib/shell/pipe_exec/fork.rs
+++ b/src/lib/shell/pipe_exec/fork.rs
@@ -12,12 +12,7 @@ impl<'a> Shell<'a> {
 
     /// Forks the shell, adding the child to the parent's background list, and executing
     /// the given commands in the child fork.
-    pub(super) fn fork_pipe(
-        &mut self,
-        pipeline: Pipeline<'a>,
-        command_name: String,
-        state: ProcessState,
-    ) -> Status {
+    pub(super) fn fork_pipe(&mut self, pipeline: Pipeline<'a>, state: ProcessState) -> Status {
         match unsafe { sys::fork() } {
             Ok(0) => {
                 self.opts_mut().is_background_shell = true;
@@ -42,7 +37,11 @@ impl<'a> Shell<'a> {
             Ok(pid) => {
                 if state != ProcessState::Empty {
                     // The parent process should add the child fork's PID to the background.
-                    self.send_to_background(BackgroundProcess::new(pid, state, command_name));
+                    self.send_to_background(BackgroundProcess::new(
+                        pid,
+                        state,
+                        pipeline.to_string(),
+                    ));
                 }
                 Status::SUCCESS
             }
diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs
index 884346ec..a4a1c7df 100644
--- a/src/lib/shell/pipe_exec/mod.rs
+++ b/src/lib/shell/pipe_exec/mod.rs
@@ -65,9 +65,11 @@ pub enum RedirectError {
 #[derive(Debug, Error)]
 pub enum PipeError {
     #[error(display = "ion: {}", cause)]
-    RedirectError { cause: RedirectError },
+    RedirectPipeError { cause: RedirectError },
     #[error(display = "ion: could not create pipe: {}", cause)]
-    CreateError { cause: io::Error },
+    CreatePipeError { cause: io::Error },
+    #[error(display = "ion: could not run function: {}", cause)]
+    RunFunctionError { cause: FunctionError },
 }
 
 impl fmt::Display for OutputError {
@@ -100,7 +102,11 @@ impl From<InputError> for RedirectError {
 }
 
 impl From<RedirectError> for PipeError {
-    fn from(cause: RedirectError) -> Self { PipeError::RedirectError { cause } }
+    fn from(cause: RedirectError) -> Self { PipeError::RedirectPipeError { cause } }
+}
+
+impl From<FunctionError> for PipeError {
+    fn from(cause: FunctionError) -> Self { PipeError::RunFunctionError { cause } }
 }
 
 /// Create an OS pipe and write the contents of a byte slice to one end
@@ -332,15 +338,7 @@ impl<'b> Shell<'b> {
         if let Some(Value::Function(function)) = self.variables.get_ref(name).cloned() {
             match function.execute(self, args) {
                 Ok(()) => Status::SUCCESS,
-                Err(FunctionError::InvalidArgumentCount) => {
-                    Status::error(format!("ion: invalid number of function arguments supplied"))
-                }
-                Err(FunctionError::InvalidArgumentType(expected_type, value)) => {
-                    Status::error(format!(
-                        "ion: function argument has invalid type: expected {}, found value \'{}\'",
-                        expected_type, value
-                    ))
-                }
+                Err(why) => Status::error(format!("{}", why)),
             }
         } else {
             unreachable!()
@@ -409,31 +407,17 @@ impl<'b> Shell<'b> {
     /// If a job is stopped, the shell will add that job to a list of background jobs and
     /// continue to watch the job in the background, printing notifications on status changes
     /// of that job over time.
-    pub fn execute_pipeline(&mut self, pipeline: Pipeline<'b>) -> Status {
-        // Don't execute commands when the `-n` flag is passed.
-        if self.opts.no_exec {
-            return Status::SUCCESS;
-        }
-
-        // A string representing the command is stored here.
-        let command = pipeline.to_string();
-        if self.opts.print_comms {
-            eprintln!("> {}", command);
-        }
-
+    pub fn execute_pipeline(&mut self, pipeline: Pipeline<'b>) -> Result<Status, PipeError> {
         // While active, the SIGTTOU signal will be ignored.
         let _sig_ignore = SignalHandler::new();
 
         // If the given pipeline is a background task, fork the shell.
         match pipeline.pipe {
-            PipeType::Disown => self.fork_pipe(pipeline, command, ProcessState::Empty),
-            PipeType::Background => self.fork_pipe(pipeline, command, ProcessState::Running),
+            PipeType::Disown => Ok(self.fork_pipe(pipeline, ProcessState::Empty)),
+            PipeType::Background => Ok(self.fork_pipe(pipeline, ProcessState::Running)),
             // Execute each command in the pipeline, giving each command the foreground.
             PipeType::Normal => {
-                let exit_status = self.pipe(pipeline).unwrap_or_else(|err| {
-                    eprintln!("{}", err);
-                    Status::COULD_NOT_EXEC
-                });
+                let exit_status = self.pipe(pipeline);
                 // Set the shell as the foreground process again to regain the TTY.
                 if !self.opts.is_background_shell {
                     let _ = sys::tcsetpgrp(0, process::id());
@@ -484,7 +468,7 @@ impl<'b> Shell<'b> {
                     } else {
                         // Pipe the previous command's stdin to this commands stdout/stderr.
                         let (reader, writer) = sys::pipe2(sys::O_CLOEXEC)
-                            .map_err(|cause| PipeError::CreateError { cause })?;
+                            .map_err(|cause| PipeError::CreatePipeError { cause })?;
                         if is_external {
                             append_external_stdio_pipe(&mut ext_stdio_pipes, writer);
                         }
@@ -664,7 +648,7 @@ where
             *current_pid = pid;
             Ok(())
         }
-        Err(cause) => Err(PipeError::CreateError { cause }),
+        Err(cause) => Err(PipeError::CreatePipeError { cause }),
     }
 }
 
diff --git a/src/lib/shell/pipe_exec/pipes.rs b/src/lib/shell/pipe_exec/pipes.rs
index aab2550b..ed73646c 100644
--- a/src/lib/shell/pipe_exec/pipes.rs
+++ b/src/lib/shell/pipe_exec/pipes.rs
@@ -26,7 +26,7 @@ impl<'a, 'b> TeePipe<'a, 'b> {
         F: FnMut(&mut RefinedJob<'b>, File),
     {
         let (reader, writer) =
-            sys::pipe2(sys::O_CLOEXEC).map_err(|cause| PipeError::CreateError { cause })?;
+            sys::pipe2(sys::O_CLOEXEC).map_err(|cause| PipeError::CreatePipeError { cause })?;
         (*tee).source = Some(unsafe { File::from_raw_fd(reader) });
         action(self.parent, unsafe { File::from_raw_fd(writer) });
         if self.is_external {
diff --git a/src/lib/shell/pipe_exec/streams.rs b/src/lib/shell/pipe_exec/streams.rs
index a4c65ae6..0736557c 100644
--- a/src/lib/shell/pipe_exec/streams.rs
+++ b/src/lib/shell/pipe_exec/streams.rs
@@ -23,12 +23,12 @@ pub fn duplicate_streams() -> Result<(Option<File>, File, File), PipeError> {
 
     let stdout = unsafe {
         File::from_raw_fd(
-            sys::dup(sys::STDOUT_FILENO).map_err(|cause| PipeError::CreateError { cause })?,
+            sys::dup(sys::STDOUT_FILENO).map_err(|cause| PipeError::CreatePipeError { cause })?,
         )
     };
     let stderr = unsafe {
         File::from_raw_fd(
-            sys::dup(sys::STDERR_FILENO).map_err(|cause| PipeError::CreateError { cause })?,
+            sys::dup(sys::STDERR_FILENO).map_err(|cause| PipeError::CreatePipeError { cause })?,
         )
     };
     // And then meld stderr alongside stdin and stdout
-- 
GitLab