dstest 0.1.1

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

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

use crate::bindings::context::BindingContext;
use crate::substrate::docker::DockerSubjectData;

pub fn register(lua: &Lua, dstest: &Table, ctx: &BindingContext) -> Result<()> {
    let state = Arc::clone(&ctx.state);
    let docker = Arc::clone(&ctx.docker);
    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 = substrate_type
            .or_else(|| cfg.substrate.clone())
            .ok_or_else(|| {
                mlua::Error::RuntimeError(
                    "no substrate configured: call dstest.config({ substrate = \"docker\" }) first"
                        .to_string(),
                )
            })?;

        match substrate.as_str() {
            "docker" => {
                let image: String = config_tbl.get("image")?;
                let ports: Option<Vec<u16>> = config_tbl.get("ports").ok();
                let cmd: Option<Vec<String>> = config_tbl.get("cmd").ok();
                let volumes: Option<Vec<String>> = config_tbl.get("volumes").ok();
                let env: Option<HashMap<String, String>> = config_tbl.get("env").ok();
                let env = env.map(|e| e.into_iter().map(|(k, v)| format!("{}={}", k, v)).collect());

                let data = DockerSubjectData {
                    image,
                    cmd,
                    ports,
                    volumes,
                    env,
                };

                let container_id = docker.host(&data).map_err(mlua::Error::RuntimeError)?;

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

                if let Some(ref p) = data.ports.as_ref().and_then(|ports| ports.first()) {
                    state.subject_hosts.insert(
                        format!("docker/{}", container_id),
                        format!("localhost:{}", p),
                    );
                }

                state
                    .subjects
                    .push((format!("docker/{}", container_id), data));

                Ok(format!("docker/{}", container_id))
            }
            _ => Err(mlua::Error::RuntimeError(format!(
                "unknown substrate type: {}",
                substrate
            ))),
        }
    })?;

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