A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
7.1 KiB

use crate::loggers::Logger;
use crate::symbols::Symbol;
use std::cell::RefCell;
use std::error::Error;
use std::fmt;
use std::fmt::Debug;
pub trait SymbolRunner {
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>>;
}
impl<R: SymbolRunner + ?Sized> SymbolRunner for Box<R> {
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
(**self).run_symbol(symbol, force)
}
}
#[derive(Debug)]
pub enum SymbolRunError {
Symbol(Box<dyn Error>),
ExecuteDidNotReach(()),
}
impl Error for SymbolRunError {
fn cause(&self) -> Option<&dyn Error> {
match self {
Self::Symbol(ref e) => Some(&**e),
Self::ExecuteDidNotReach(_) => None,
}
}
}
impl fmt::Display for SymbolRunError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Symbol(ref e) => write!(f, "{}", e),
Self::ExecuteDidNotReach(_) => write!(f, "Target not reached after executing symbol"),
}
}
}
pub struct InitializingSymbolRunner<L: Logger> {
logger: RefCell<L>,
}
impl<L: Logger> InitializingSymbolRunner<L> {
pub fn new(logger: L) -> Self {
Self {
logger: RefCell::new(logger),
}
}
fn exec_symbol<S: Symbol + Debug>(&self, symbol: &S) -> Result<(), Box<dyn Error>> {
let mut logger = self.logger.borrow_mut();
logger.write(format!("Executing {:?}", symbol).as_str());
symbol.execute()?;
let target_reached = symbol.target_reached()?;
logger.debug(
format!(
"Symbol reports target_reached: {:?} (should be true)",
target_reached
)
.as_str(),
);
if target_reached {
Ok(())
} else {
Err(Box::new(SymbolRunError::ExecuteDidNotReach(())))
}
}
}
impl<L: Logger> SymbolRunner for InitializingSymbolRunner<L> {
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
let mut logger = self.logger.borrow_mut();
let executed = if force {
logger.debug("Forcing symbol execution");
drop(logger);
self.exec_symbol(symbol)?;
true
} else {
let target_reached = symbol.target_reached()?;
if target_reached {
logger.write(format!("{:?} already reached", symbol).as_str());
} else {
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
drop(logger);
self.exec_symbol(symbol)?;
}
!target_reached
};
Ok(executed)
}
}
pub struct DrySymbolRunner<L: Logger> {
logger: RefCell<L>,
}
impl<L: Logger> DrySymbolRunner<L> {
pub fn new(logger: L) -> Self {
Self {
logger: RefCell::new(logger),
}
}
}
impl<L: Logger> SymbolRunner for DrySymbolRunner<L> {
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
let mut logger = self.logger.borrow_mut();
let would_execute = if force {
logger.write(format!("Would force-execute {:?}", symbol).as_str());
true
} else {
let target_reached = symbol.target_reached()?;
logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str());
if !target_reached {
logger.write(format!("Would execute {:?}", symbol).as_str());
}
!target_reached
};
Ok(would_execute)
}
}
pub struct ReportingSymbolRunner<'a, R, L>(&'a R, RefCell<L>);
impl<'a, R, L> ReportingSymbolRunner<'a, R, L> {
pub fn new(symbol_runner: &'a R, logger: L) -> Self {
ReportingSymbolRunner(symbol_runner, RefCell::new(logger))
}
}
impl<'a, R, L> SymbolRunner for ReportingSymbolRunner<'a, R, L>
where
R: SymbolRunner,
L: Logger,
{
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
let mut logger = self.1.borrow_mut();
logger.debug(format!("Running symbol {:?}", symbol).as_str());
let res = self.0.run_symbol(symbol, force);
if let Err(ref e) = res {
logger.write(format!("Failed on {:?} with {}, aborting.", symbol, e).as_str())
} else {
logger.debug(format!("Successfully finished {:?}", symbol).as_str())
}
res
}
}
#[cfg(test)]
mod test {
use std::cell::RefCell;
use std::error::Error;
use std::fmt;
use crate::loggers::Logger;
use crate::schema::InitializingSymbolRunner;
use crate::schema::SymbolRunner;
use crate::symbols::Symbol;
#[derive(Debug, PartialEq, Clone)]
enum DummySymbolError {
Error(()),
}
impl Error for DummySymbolError {}
impl fmt::Display for DummySymbolError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Dummy symbol error")
}
}
#[derive(Debug)]
struct DummySymbol<T, E> {
_target_reached: RefCell<T>,
_execute: RefCell<E>,
}
impl<
E: Iterator<Item = Result<(), Box<dyn Error>>>,
T: Iterator<Item = Result<bool, Box<dyn Error>>>,
> Symbol for DummySymbol<T, E>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
self._target_reached.borrow_mut().next().unwrap()
}
fn execute(&self) -> Result<(), Box<dyn Error>> {
self._execute.borrow_mut().next().unwrap()
}
}
impl<
E: Iterator<Item = Result<(), Box<dyn Error>>>,
T: Iterator<Item = Result<bool, Box<dyn Error>>>,
> DummySymbol<T, E>
{
fn new<
IE: IntoIterator<IntoIter = E, Item = Result<(), Box<dyn Error>>>,
IT: IntoIterator<IntoIter = T, Item = Result<bool, Box<dyn Error>>>,
>(
target_reached: IT,
execute: IE,
) -> Self {
Self {
_target_reached: RefCell::new(target_reached.into_iter()),
_execute: RefCell::new(execute.into_iter()),
}
}
}
struct DummyLogger {
log: Vec<String>,
}
impl DummyLogger {
fn new() -> DummyLogger {
DummyLogger { log: Vec::new() }
}
}
impl Logger for DummyLogger {
fn write(&mut self, line: &str) {
self.log.push(line.into());
}
fn debug(&mut self, line: &str) {
self.log.push(line.into());
}
}
#[test]
fn nothing_needed_to_be_done() {
let result = InitializingSymbolRunner::new(DummyLogger::new())
.run_symbol(&DummySymbol::new(vec![Ok(true)], vec![Ok(())]), false);
assert!(result.is_ok());
}
#[test]
fn everything_is_ok() {
let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(
&DummySymbol::new(vec![Ok(true), Ok(false)], vec![Ok(())]),
false,
);
assert!(result.is_ok());
}
#[test]
fn executing_did_not_change_state() {
let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(
&DummySymbol::new(vec![Ok(false), Ok(false)], vec![Ok(())]),
false,
);
assert_eq!(
result.unwrap_err().to_string(),
"Target not reached after executing symbol"
);
}
#[test]
fn executing_did_not_work() {
let err = InitializingSymbolRunner::new(DummyLogger::new())
.run_symbol(
&DummySymbol::new(
vec![Ok(false)],
vec![Err(Box::new(DummySymbolError::Error(())) as Box<dyn Error>)],
),
false,
)
.unwrap_err();
assert_eq!(err.to_string(), "Dummy symbol error");
}
}