use crate::qcow2; use crate::qcow2::{BuildBaseResult, Qcow2Error, Qcow2Snapshot}; use rhai::{Array, Dynamic, Engine, EvalAltResult, Map}; use serde_json::Value; // Convert Qcow2Error to Rhai error fn qcow2_error_to_rhai(result: Result) -> Result> { result.map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!("qcow2 error: {}", e).into(), rhai::Position::NONE, )) }) } // Convert serde_json::Value to Rhai Dynamic recursively (maps, arrays, scalars) fn json_to_dynamic(v: &Value) -> Dynamic { match v { Value::Null => Dynamic::UNIT, Value::Bool(b) => (*b).into(), Value::Number(n) => { if let Some(i) = n.as_i64() { i.into() } else { // Avoid float dependency differences; fall back to string n.to_string().into() } } Value::String(s) => s.clone().into(), Value::Array(arr) => { let mut a = Array::new(); for item in arr { a.push(json_to_dynamic(item)); } a.into() } Value::Object(obj) => { let mut m = Map::new(); for (k, val) in obj { m.insert(k.into(), json_to_dynamic(val)); } m.into() } } } // Wrappers exposed to Rhai pub fn qcow2_create(path: &str, size_gb: i64) -> Result> { qcow2_error_to_rhai(qcow2::create(path, size_gb)) } pub fn qcow2_info(path: &str) -> Result> { let v = qcow2_error_to_rhai(qcow2::info(path))?; Ok(json_to_dynamic(&v)) } pub fn qcow2_snapshot_create(path: &str, name: &str) -> Result<(), Box> { qcow2_error_to_rhai(qcow2::snapshot_create(path, name)) } pub fn qcow2_snapshot_delete(path: &str, name: &str) -> Result<(), Box> { qcow2_error_to_rhai(qcow2::snapshot_delete(path, name)) } pub fn qcow2_snapshot_list(path: &str) -> Result> { let snaps = qcow2_error_to_rhai(qcow2::snapshot_list(path))?; let mut arr = Array::new(); for s in snaps { arr.push(snapshot_to_map(&s).into()); } Ok(arr) } fn snapshot_to_map(s: &Qcow2Snapshot) -> Map { let mut m = Map::new(); if let Some(id) = &s.id { m.insert("id".into(), id.clone().into()); } else { m.insert("id".into(), Dynamic::UNIT); } if let Some(name) = &s.name { m.insert("name".into(), name.clone().into()); } else { m.insert("name".into(), Dynamic::UNIT); } if let Some(v) = s.vm_state_size { m.insert("vm_state_size".into(), v.into()); } else { m.insert("vm_state_size".into(), Dynamic::UNIT); } if let Some(v) = s.date_sec { m.insert("date_sec".into(), v.into()); } else { m.insert("date_sec".into(), Dynamic::UNIT); } if let Some(v) = s.date_nsec { m.insert("date_nsec".into(), v.into()); } else { m.insert("date_nsec".into(), Dynamic::UNIT); } if let Some(v) = s.vm_clock_nsec { m.insert("vm_clock_nsec".into(), v.into()); } else { m.insert("vm_clock_nsec".into(), Dynamic::UNIT); } m } pub fn qcow2_build_ubuntu_24_04_base( dest_dir: &str, size_gb: i64, ) -> Result> { // size_gb: pass None if <=0 let size_opt = if size_gb > 0 { Some(size_gb) } else { None }; let r: BuildBaseResult = qcow2_error_to_rhai(qcow2::build_ubuntu_24_04_base(dest_dir, size_opt))?; let mut m = Map::new(); m.insert("base_image_path".into(), r.base_image_path.into()); m.insert("snapshot".into(), r.snapshot.into()); m.insert("url".into(), r.url.into()); if let Some(sz) = r.resized_to_gb { m.insert("resized_to_gb".into(), sz.into()); } else { m.insert("resized_to_gb".into(), Dynamic::UNIT); } Ok(m) } // Module registration pub fn register_qcow2_module(engine: &mut Engine) -> Result<(), Box> { engine.register_fn("qcow2_create", qcow2_create); engine.register_fn("qcow2_info", qcow2_info); engine.register_fn("qcow2_snapshot_create", qcow2_snapshot_create); engine.register_fn("qcow2_snapshot_delete", qcow2_snapshot_delete); engine.register_fn("qcow2_snapshot_list", qcow2_snapshot_list); engine.register_fn("qcow2_build_ubuntu_24_04_base", qcow2_build_ubuntu_24_04_base); Ok(()) }