cleanlib-cli 0.1.0

Terminal interface to CleanLibrary — query dependency verdicts and scan package manifests for ALLOW / DENY / WARN signals from the terminal or CI pipelines.
//! `cleanlib policy preview` (cycle-7 Cli2). Migrates `cmd_policy_preview`
//! and `parse_policy_file` out of `main.rs`.

use std::path::{Path, PathBuf};

use anyhow::Result;
use cleanlib_client::{config, transport, types};

use crate::render::terminal;

use super::scan::parse_packages_file;
use super::scan_exit_code;

pub async fn preview(
    policy_path: PathBuf,
    packages_path: PathBuf,
    ecosystem: String,
    output: String,
) -> Result<()> {
    let path = config::default_path();
    let cfg = config::load_with_env_overrides(path.as_deref())?;
    let client = transport::Client::from_config(&cfg)?;

    let packages = parse_packages_file(&packages_path, &ecosystem)?;
    let policy = parse_policy_file(&policy_path)?;
    let req = types::PolicyPreviewRequest {
        packages,
        policy: Some(policy),
    };
    let resp = client.policy_preview(&req).await?;

    match output.as_str() {
        "json" => println!("{}", serde_json::to_string_pretty(&resp)?),
        _ => terminal::render_decisions(&resp.decisions),
    }

    let code = scan_exit_code(&resp.decisions);
    if code != 0 {
        std::process::exit(code);
    }
    Ok(())
}

/// Parse a policy file — accepts JSON or TOML; TOML converts to a
/// `serde_json::Value` representation. YAML deferred (would require yaml dep).
pub fn parse_policy_file(path: &Path) -> Result<serde_json::Value> {
    let content = std::fs::read_to_string(path)
        .map_err(|e| anyhow::anyhow!("read {}: {}", path.display(), e))?;
    // Try JSON first
    if let Ok(v) = serde_json::from_str::<serde_json::Value>(&content) {
        return Ok(v);
    }
    // Fall back to TOML
    let toml_val: toml::Value = toml::from_str(&content)
        .map_err(|e| anyhow::anyhow!("{}: not valid JSON; TOML parse: {}", path.display(), e))?;
    let json_val = serde_json::to_value(toml_val)
        .map_err(|e| anyhow::anyhow!("{}: TOML→JSON conversion: {}", path.display(), e))?;
    Ok(json_val)
}

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

    #[test]
    fn parse_json_policy() {
        let dir = std::env::temp_dir();
        let p = dir.join("cleanlib-policy-json-test.json");
        std::fs::write(&p, r#"{"rules": [{"id": 1}]}"#).unwrap();
        let v = parse_policy_file(&p).unwrap();
        assert_eq!(v["rules"][0]["id"], 1);
        let _ = std::fs::remove_file(&p);
    }

    #[test]
    fn parse_toml_policy() {
        let dir = std::env::temp_dir();
        let p = dir.join("cleanlib-policy-toml-test.toml");
        std::fs::write(&p, "[meta]\nname = \"strict\"\n").unwrap();
        let v = parse_policy_file(&p).unwrap();
        assert_eq!(v["meta"]["name"], "strict");
        let _ = std::fs::remove_file(&p);
    }
}