dstest 0.1.6

Deterministic Simulation Testing for containerised services
use std::sync::Arc;

use mlua::{Lua, Result, Table};

use crate::engine::context::BindingContext;
use crate::substrate::Substrate;

pub fn register<S: Substrate>(lua: &Lua, dstest: &Table, ctx: &BindingContext<S>) -> Result<()> {
    let state = Arc::clone(&ctx.state);
    let substrate = Arc::clone(&ctx.substrate);
    let config = Arc::clone(&ctx.config);

    let setup = lua.create_function(move |lua, args: mlua::MultiValue| {
        let mut args_iter = args.into_iter();
        let first = args_iter.next();
        let second = args_iter.next();

        let (substrate_type, config_tbl): (Option<String>, Table) = match (first, second) {
            (Some(mlua::Value::String(s)), Some(mlua::Value::Table(t))) => {
                (Some(s.to_str()?.to_owned()), t)
            }
            (Some(mlua::Value::Table(t)), None) => (None, t),
            (Some(mlua::Value::String(s)), None) => {
                let s = s.to_str()?.to_owned();
                let t = lua.create_table()?;
                (Some(s), t)
            }
            _ => {
                return Err(mlua::Error::RuntimeError(
                    "dstest.setup expects (table) or (string, table)".to_string(),
                ));
            }
        };

        let cfg = config.lock().expect("poisoned config lock");
        let substrate_name = substrate_type
            .or_else(|| cfg.substrate.clone())
            .ok_or_else(|| {
                mlua::Error::RuntimeError(
                    "no substrate configured: call dstest.config({ substrate = \"docker\" }) first"
                        .to_string(),
                )
            })?;
        drop(cfg);

        if substrate_name != S::NAME {
            return Err(mlua::Error::RuntimeError(format!(
                "substrate mismatch: config wants \"{}\" but engine was built for \"{}\"",
                substrate_name,
                S::NAME
            )));
        }

        let data = substrate
            .parse_subject(&config_tbl)
            .map_err(mlua::Error::RuntimeError)?;

        let hosted = substrate.host(&data).map_err(mlua::Error::RuntimeError)?;

        let subject_id = format!("{}/{}", S::NAME, hosted.id);

        let mut state = state.lock().expect("poisoned engine state lock");

        if let Some(addr) = hosted.addr {
            state.subject_hosts.insert(subject_id.clone(), addr);
        }

        state.subjects.push((subject_id.clone(), data));

        Ok(subject_id)
    })?;

    dstest.set("setup", setup)?;
    Ok(())
}