rngo-cli 0.20.0

Data simulation CLI using api.rngo.dev
use anyhow::{Context, Result, anyhow, bail};
use serde_json::{Map, Value};
use std::fs;
use std::path::Path;

use crate::util::config::Config;

pub fn load_spec_from_file(spec_path: String) -> Result<Value> {
    let path = Path::new(&spec_path);

    if !path.exists() {
        bail!("Could not find file '{}'", spec_path)
    }

    let file_content = fs::read_to_string(path)?;
    serde_yaml::from_str(&file_content)
        .with_context(|| format!("Failed to parse spec file at {}", path.to_string_lossy()))
}

pub fn load_spec_from_project_directory(config: &Config) -> Result<Value> {
    let rngo_path = Path::new(".rngo");
    let effects_path = rngo_path.join("effects");

    let effect_files = fs::read_dir(effects_path.clone()).with_context(|| {
        format!(
            "Failed to read from effects directory at '{}'",
            effects_path.to_string_lossy()
        )
    })?;

    let mut effects_map = Map::new();

    for entry in effect_files {
        let entry = entry?;
        let path = entry.path();

        let content = fs::read_to_string(&path)?;
        let yaml_value: serde_yaml::Value = serde_yaml::from_str(&content).with_context(|| {
            format!("Failed to parse effect file at {}", path.to_string_lossy())
        })?;
        let mut json_value: serde_json::Value = serde_json::to_value(yaml_value)?;

        if let Some(obj) = json_value.as_object_mut() {
            obj.entry("type").or_insert_with(|| "state.create".into());
        }

        if let Some(filename) = path.file_stem().and_then(|s| s.to_str()) {
            effects_map.insert(filename.to_string(), json_value);
        }
    }

    if effects_map.is_empty() {
        bail!(
            "No effects found under {}",
            effects_path.to_string_lossy()
        )
    }

    let systems_map = load_systems_from_project_directory()?;

    let mut spec = Map::new();
    spec.insert("seed".into(), config.seed.into());

    if let Some(key) = &config.key {
        spec.insert("key".into(), key.clone().into());
    } else {
        let dir_name = std::env::current_dir()
            .ok()
            .and_then(|dir| {
                dir.file_name()
                    .and_then(|s| s.to_str())
                    .map(|s| s.to_string())
            })
            .ok_or_else(|| anyhow!("Failed to get current directory"))?;

        spec.insert("key".into(), dir_name.into());
    }

    if let Some(start) = &config.start {
        spec.insert("start".into(), start.clone().into());
    }

    if let Some(end) = &config.end {
        spec.insert("end".into(), end.clone().into());
    }

    if !systems_map.is_empty() {
        spec.insert("systems".into(), serde_json::Value::Object(systems_map));
    }
    spec.insert("effects".into(), serde_json::Value::Object(effects_map));

    Ok(serde_json::Value::Object(spec))
}

pub fn load_systems_from_project_directory() -> Result<Map<String, Value>> {
    let rngo_path = Path::new(".rngo");
    let systems_path = rngo_path.join("systems");
    let mut systems_map = Map::new();

    if systems_path.is_dir() {
        let system_files = fs::read_dir(systems_path.clone()).with_context(|| {
            format!(
                "Failed to read from systems directory at '{}'",
                systems_path.to_string_lossy()
            )
        })?;

        for system in system_files {
            let system = system?;
            let path = system.path();

            let content = fs::read_to_string(&path)?;
            let yaml_value: serde_yaml::Value = serde_yaml::from_str(&content)
                .with_context(|| format!("Failed to parse file at {}", path.to_string_lossy()))?;
            let json_value: serde_json::Value = serde_json::to_value(yaml_value)?;

            if let Some(filename) = path.file_stem().and_then(|s| s.to_str()) {
                systems_map.insert(filename.to_string(), json_value);
            }
        }
    }

    Ok(systems_map)
}

pub fn ensure_spec_output_is_stream(mut spec: Value) -> Value {
    match spec {
        Value::Object(ref mut map) => {
            map.insert("output".into(), "stream".into());
            spec
        }
        _ => spec,
    }
}

pub fn get_spec_key(spec: &mut Value) -> Option<String> {
    match spec {
        Value::Object(map) => {
            if let Some(key) = map.remove("key") {
                key.as_str().map(|s| s.to_string())
            } else {
                None
            }
        }
        _ => None,
    }
}