mumu-json 0.1.0

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

use std::ffi::c_void;

use indexmap::IndexMap;
use mumu::parser::interpreter::Interpreter;
use mumu::parser::types::Value;
use serde_json::{Map as JsonMap, Value as JsonVal};

mod codec;     // json:encode / json:decode
mod schema;    // json:schema (schema-based validation)
mod validate;  // json:validate  (returns Bool)
mod report;    // json:report    (returns Bool | StrArray)

/* ───────────────────────── Plugin entry-point ─────────────────────────── */
#[export_name = "Cargo_lock"]
pub unsafe extern "C" fn cargo_lock(
    interp_ptr: *mut c_void,
    extra_str: *const c_void,
) -> i32 {
    if interp_ptr.is_null() {
        eprintln!("[json plugin] null interpreter pointer");
        return 1;
    }
    let interpreter = &mut *(interp_ptr as *mut Interpreter);

    if interpreter.is_verbose() {
        if !extra_str.is_null() {
            use std::ffi::CStr;
            let extra = CStr::from_ptr(extra_str as *const i8).to_string_lossy();
            eprintln!("[json plugin] loading (extra = \"{extra}\")");
        } else {
            eprintln!("[json plugin] loading");
        }
    }

    /* register every public symbol */
    codec::register_json_decode(interpreter);
    codec::register_json_encode(interpreter);
    schema::register_json_schema(interpreter);
    validate::register_json_validate(interpreter);
    report::register_json_report(interpreter);

    if interpreter.is_verbose() {
        eprintln!("[json plugin] ready");
    }
    0
}

/* ═══════════════════ shared helpers (used across modules) ═══════════════ */
/// serde_json → mumu::Value
pub fn jsonval_to_mumu(jv: &JsonVal) -> Value {
    match jv {
        JsonVal::Null => Value::Placeholder,
        JsonVal::Bool(b) => Value::Bool(*b),
        JsonVal::Number(n) => {
            if let Some(i) = n.as_i64() {
                if (i32::MIN as i64..=i32::MAX as i64).contains(&i) {
                    Value::Int(i as i32)
                } else {
                    Value::Long(i)
                }
            } else if let Some(f) = n.as_f64() {
                Value::Float(f)
            } else {
                Value::Placeholder
            }
        }
        JsonVal::String(s) => Value::SingleString(s.clone()),
        JsonVal::Array(arr) => {
            if arr.iter().all(|v| v.is_i64()) {
                Value::IntArray(arr.iter().map(|v| v.as_i64().unwrap() as i32).collect())
            } else if arr.iter().all(|v| v.is_f64()) {
                Value::FloatArray(arr.iter().map(|v| v.as_f64().unwrap()).collect())
            } else if arr.iter().all(|v| v.is_string()) {
                Value::StrArray(arr.iter().map(|v| v.as_str().unwrap().to_string()).collect())
            } else {
                let mut map = IndexMap::new();
                for (i, sub) in arr.iter().enumerate() {
                    map.insert(i.to_string(), jsonval_to_mumu(sub));
                }
                Value::KeyedArray(map)
            }
        }
        JsonVal::Object(obj) => {
            let mut map = IndexMap::new();
            for (k, v) in obj {
                map.insert(k.clone(), jsonval_to_mumu(v));
            }
            Value::KeyedArray(map)
        }
    }
}

/// mumu::Value → serde_json
pub fn mumu_value_to_json(v: &Value) -> JsonVal {
    match v {
        Value::Placeholder => JsonVal::Null,
        Value::Bool(b) => JsonVal::Bool(*b),
        Value::Int(i) => JsonVal::Number((*i).into()),
        Value::Long(l) => JsonVal::Number((*l).into()),
        Value::Float(f) => JsonVal::Number(
            serde_json::Number::from_f64(*f).unwrap_or_else(|| 0.into())
        ),
        Value::SingleString(s) => JsonVal::String(s.clone()),
        Value::IntArray(xs) => JsonVal::Array(
            xs.iter().map(|n| JsonVal::Number((*n).into())).collect()
        ),
        Value::FloatArray(fs) => JsonVal::Array(
            fs.iter()
                .map(|f| JsonVal::Number(
                    serde_json::Number::from_f64(*f).unwrap_or_else(|| 0.into())
                ))
                .collect(),
        ),
        Value::BoolArray(bs) => JsonVal::Array(bs.iter().map(|b| JsonVal::Bool(*b)).collect()),
        Value::StrArray(ss) => JsonVal::Array(ss.iter().map(|s| JsonVal::String(s.clone())).collect()),
        Value::Int2DArray(rows) => JsonVal::Array(rows.iter().map(|r|
            JsonVal::Array(r.iter().map(|n| JsonVal::Number((*n).into())).collect())
        ).collect()),
        Value::Float2DArray(rows) => JsonVal::Array(rows.iter().map(|r|
            JsonVal::Array(r.iter().map(|f|
                JsonVal::Number(serde_json::Number::from_f64(*f).unwrap_or_else(|| 0.into()))
            ).collect())
        ).collect()),
        Value::KeyedArray(map) => {
            let mut obj = JsonMap::new();
            for (k, v) in map {
                obj.insert(k.clone(), mumu_value_to_json(v));
            }
            JsonVal::Object(obj)
        }
        _ => JsonVal::Null,
    }
}