eggsecutor 2.0.0

A simple and lightweight stateful daemon tracking CLI
use clap::{Error, ErrorKind};
use std::io;

pub fn handle_spawn_failure(err_reason: io::Error) -> ! {
    get_spawn_failure_error(err_reason).exit();
}

pub fn handle_no_file_data_error() -> ! {
    get_no_file_data_error().exit();
}

pub fn handle_no_such_process_error(process_info: &str) -> ! {
    get_no_such_process_error(process_info).exit();
}

pub fn handle_process_boot_error(err_reason: io::Error) -> ! {
    get_process_boot_error(err_reason).exit();
}

pub fn get_invalid_file_path_error() -> Error {
    Error::with_description(
        "invalid path to binary: file does not exist or is inaccessible".to_string(),
        ErrorKind::InvalidValue,
    )
}

fn get_spawn_failure_error(err_reason: io::Error) -> Error {
    Error::with_description(
        format!(
            "could not hatch process: binary could not be executed. Details: {}",
            err_reason
        ),
        ErrorKind::Io,
    )
}

fn get_no_file_data_error() -> Error {
    Error::with_description(
        "no state file data found. Add a process to track first".to_string(),
        ErrorKind::Io,
    )
}

fn get_no_such_process_error(process_info: &str) -> Error {
    Error::with_description(
        format!(
            r#"couldn not stop process. no matching process with identifier: "{}""#,
            process_info
        ),
        ErrorKind::InvalidValue,
    )
}

fn get_process_boot_error(err_reason: io::Error) -> Error {
    Error::with_description(
        format!(
            "process abruptly exited after being hatched, details: {}",
            err_reason
        ),
        ErrorKind::Io,
    )
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io;

    #[test]
    fn process_boot_error_should_return_io_clap_err() {
        let kind = clap::ErrorKind::Io;
        let err_msg = "test boot error";
        let io_err = get_io_error(err_msg);

        let clap_err_fn = || get_process_boot_error(io_err);

        check_err_matches_spec(err_msg, kind, clap_err_fn);
    }

    #[test]
    fn no_such_process_error_should_return_invalid_value_clap_err() {
        let process_err_msg = "test process not found error";
        let kind = clap::ErrorKind::InvalidValue;

        let clap_err_fn = || get_no_such_process_error(process_err_msg);
        check_err_matches_spec(process_err_msg, kind, clap_err_fn);
    }

    #[test]
    fn no_file_data_error_should_return_clap_io_err() {
        let process_err_msg = "no state file data found. Add a process to track first";
        let kind = ErrorKind::Io;

        let clap_err_fn = || get_no_file_data_error();
        check_err_matches_spec(process_err_msg, kind, clap_err_fn);
    }

    #[test]
    fn spawn_failure_error_should_return_clap_io_error() {
        let kind = ErrorKind::Io;
        let process_err_msg = "test spawn error";
        let io_err = get_io_error(process_err_msg);

        let clap_err_fn = || get_spawn_failure_error(io_err);

        check_err_matches_spec(process_err_msg, kind, clap_err_fn);
    }

    #[test]
    fn invalid_file_path_error_should_return_invalid_value_clap_error() {
        let kind = ErrorKind::InvalidValue;
        let process_err_msg = "invalid path to binary: file does not exist or is inaccessible";

        let clap_err_fn = || get_invalid_file_path_error();

        check_err_matches_spec(process_err_msg, kind, clap_err_fn);
    }

    fn check_err_matches_spec<F>(err_msg: &str, error_kind: ErrorKind, err_factory: F)
    where
        F: FnOnce() -> clap::Error,
    {
        let clap_err = err_factory();

        assert!(clap_err.to_string().contains(err_msg));
        assert_eq!(clap_err.kind, error_kind);
    }

    fn get_io_error(err_msg: &str) -> io::Error {
        io::Error::new(io::ErrorKind::Other, err_msg)
    }
}