mumu-json 0.1.0

JSON tools and JSON Scheam plugin for the Lava language
Documentation
// FILE: json/src/report.rs

//! `json:report` – parse-only validation **with diagnostics**
//!
//! * **Valid JSON**   → `Bool(true)`
//! * **Invalid JSON** → `StrArray([errors…])`

use std::sync::{Arc, Mutex};

use mumu::parser::interpreter::{DynamicFn, DynamicFnInfo, Interpreter};
use mumu::parser::types::{FunctionValue, Value};

pub fn register_json_report(interp: &mut Interpreter) {
    let f: DynamicFn = Arc::new(Mutex::new(json_report_bridge));
    let info = DynamicFnInfo::new(f, true); // pure
    interp.register_dynamic_function_ex("json:report", info);
    interp.set_variable(
        "json:report",
        Value::Function(Box::new(FunctionValue::Named("json:report".to_string()))),
    );
}

fn json_report_bridge(_intp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_partial(None)),
        1 => {
            if is_placeholder(&args[0]) {
                Ok(make_partial(None))
            } else {
                Ok(report_result(&args[0]))
            }
        }
        n => Err(format!("json:report ⇒ expected ≤1 argument, got {n}")),
    }
}

/* ─────────────── partial application ─────────────────────────────────── */
#[derive(Clone)]
struct PartialState {
    arg: Option<Value>,
}

fn make_partial(arg: Option<Value>) -> Value {
    use FunctionValue::RustClosure;

    let shared = Arc::new(Mutex::new(PartialState { arg }));

    let closure: DynamicFn = Arc::new(Mutex::new(
        move |_intp: &mut Interpreter, new: Vec<Value>| -> Result<Value, String> {
            let mut st = shared
                .lock()
                .map_err(|_| "json:report partial lock error".to_string())?;

            for v in new {
                if st.arg.is_none() && !is_placeholder(&v) {
                    st.arg = Some(v);
                } else {
                    return Err("json:report partial ⇒ too many arguments".to_string());
                }
            }

            match st.arg.clone() {
                Some(v) if !is_placeholder(&v) => Ok(report_result(&v)),
                _ => Ok(make_partial(st.arg.clone())),
            }
        },
    ));

    Value::Function(Box::new(RustClosure(
        "json:report-partial".to_string(),
        closure,
        0,
    )))
}

/* ─────────────── core logic ───────────────────────────────────────────── */
fn report_result(v: &Value) -> Value {
    match v {
        Value::SingleString(s) => parse_and_report(s),
        Value::StrArray(sa) if sa.len() == 1 => parse_and_report(&sa[0]),
        _ => Value::Bool(true),
    }
}

fn parse_and_report(text: &str) -> Value {
    match serde_json::from_str::<serde_json::Value>(text) {
        Ok(_) => Value::Bool(true),
        Err(e) => Value::StrArray(vec![e.to_string()]),
    }
}

/* ─────────────── helpers ─────────────────────────────────────────────── */
fn is_placeholder(v: &Value) -> bool {
    match v {
        Value::Placeholder => true,
        Value::SingleString(s) if s == "_" => true,
        Value::StrArray(sa) if sa.len() == 1 && sa[0] == "_" => true,
        _ => false,
    }
}