//! JSON Transform — WASM guest module for ZCLAW skill system. //! //! Reads JSON from stdin, applies transforms, writes result to stdout. //! Target: `wasm32-wasi` use serde::{Deserialize, Serialize}; use serde_json::Value; use std::io::{self, Read, Write}; #[derive(Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] enum Transform { Pick { fields: Vec }, Rename { from: String, to: String }, Filter { field: String, op: String, value: Value }, Sort { field: String, order: String }, } #[derive(Deserialize)] struct Input { data: Value, #[serde(default)] transforms: Vec, } #[derive(Serialize)] struct Output { success: bool, #[serde(skip_serializing_if = "Option::is_none")] data: Option, #[serde(skip_serializing_if = "Option::is_none")] error: Option, } impl Output { fn ok(data: Value) -> Self { Self { success: true, data: Some(data), error: None, } } fn err(msg: impl Into) -> Self { Self { success: false, data: None, error: Some(msg.into()), } } } #[no_mangle] pub extern "C" fn _start() { let mut input_str = String::new(); if let Err(e) = io::stdin().read_to_string(&mut input_str) { write_output(&Output::err(format!("Failed to read stdin: {}", e))); return; } let input: Input = match serde_json::from_str(&input_str) { Ok(v) => v, Err(e) => { write_output(&Output::err(format!("Invalid JSON input: {}", e))); return; } }; let mut data = input.data; for transform in input.transforms { data = match apply_transform(data, &transform) { Ok(v) => v, Err(e) => { write_output(&Output::err(e)); return; } } } write_output(&Output::ok(data)); } fn apply_transform(data: Value, transform: &Transform) -> Result { match transform { Transform::Pick { fields } => { let obj = data.as_object().ok_or("Pick requires object input")?; let mut result = serde_json::Map::new(); for field in fields { if let Some(v) = obj.get(field) { result.insert(field.clone(), v.clone()); } } Ok(Value::Object(result)) } Transform::Rename { from, to } => { let mut obj = data.as_object().ok_or("Rename requires object input")?.clone(); if let Some(v) = obj.remove(from) { obj.insert(to.clone(), v); } Ok(Value::Object(obj)) } Transform::Filter { field, op, value } => { let arr = data.as_array().ok_or("Filter requires array input")?; let filtered: Vec = arr .iter() .filter(|item| { let item_val = item.get(field); match op.as_str() { "eq" => item_val == Some(value), "ne" => item_val != Some(value), "gt" => compare_values(item_val, value).map(|c| c.is_gt()).unwrap_or(false), "lt" => compare_values(item_val, value).map(|c| c.is_lt()).unwrap_or(false), _ => false, } }) .cloned() .collect(); Ok(Value::Array(filtered)) } Transform::Sort { field, order } => { let mut arr = data.as_array().ok_or("Sort requires array input")?.clone(); let field = field.clone(); let ascending = order != "desc"; arr.sort_by(|a, b| { let va = a.get(&field).unwrap_or(&Value::Null); let vb = b.get(&field).unwrap_or(&Value::Null); let cmp = compare_values(Some(va), vb).unwrap_or(std::cmp::Ordering::Equal); if ascending { cmp } else { cmp.reverse() } }); Ok(Value::Array(arr)) } } } fn compare_values(a: Option<&Value>, b: &Value) -> Option { let a = a?; match (a, b) { (Value::Number(an), Value::Number(bn)) => { if let (Some(af), Some(bf)) = (an.as_f64(), bn.as_f64()) { Some(af.partial_cmp(&bf).unwrap_or(std::cmp::Ordering::Equal)) } else { None } } (Value::String(as_), Value::String(bs)) => Some(as_.cmp(bs)), _ => None, } } fn write_output(output: &Output) { let json = serde_json::to_string(output).unwrap_or_else(|_| r#"{"success":false}"#.to_string()); let _ = io::stdout().write_all(json.as_bytes()); let _ = io::stdout().flush(); }