use serde_json::Value;
const DEFAULT_EXTENSIONS_ROOT: &str = "./extensions";
pub async fn run_sandbox_subprocess() -> Result<(), anyhow::Error> {
use tokio::io::AsyncReadExt;
let module_id = std::env::args()
.nth(2)
.ok_or_else(|| anyhow::anyhow!("sandbox runner: missing module_id argument"))?;
let mut stdin_buf = String::new();
tokio::io::stdin().read_to_string(&mut stdin_buf).await?;
let input_data: Value = serde_json::from_str(&stdin_buf)?;
let extensions_root = std::env::var("APCORE_EXTENSIONS_ROOT")
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| DEFAULT_EXTENSIONS_ROOT.to_string());
let registry = apcore::Registry::new();
let discoverer = crate::fs_discoverer::FsDiscoverer::new(&extensions_root);
registry.discover(&discoverer).await.map_err(|e| {
anyhow::anyhow!(
"sandbox runner: discovery failed for extensions root '{}': {}",
extensions_root,
e
)
})?;
crate::cli::set_executables(discoverer.executables_snapshot());
let config = apcore::Config::default();
let executor = apcore::Executor::new(std::sync::Arc::new(registry), config);
let result = executor.call(&module_id, input_data, None, None).await?;
let encoded = encode_result(&result);
print!("{encoded}");
Ok(())
}
pub fn encode_result(result: &Value) -> String {
serde_json::to_string(result).unwrap_or_else(|_| "null".to_string())
}
pub fn decode_result(raw: &str) -> Result<Value, serde_json::Error> {
serde_json::from_str(raw)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn encode_result_handles_object() {
let v = json!({"ok": true, "n": 42});
let s = encode_result(&v);
let parsed: Value = serde_json::from_str(&s).expect("encoder must produce valid JSON");
assert_eq!(parsed, v);
}
#[test]
fn encode_result_handles_null() {
let v = Value::Null;
assert_eq!(encode_result(&v), "null");
}
#[test]
fn encode_result_handles_array() {
let v = json!(["a", 1, null]);
assert_eq!(encode_result(&v), r#"["a",1,null]"#);
let parsed: Value = serde_json::from_str(&encode_result(&v)).unwrap();
assert_eq!(parsed, v);
}
#[test]
fn decode_result_round_trips_object() {
let original = json!({"alpha": [1, 2, 3], "beta": {"nested": "x"}});
let encoded = encode_result(&original);
let decoded = decode_result(&encoded).expect("valid JSON");
assert_eq!(decoded, original);
}
#[test]
fn decode_result_rejects_invalid_json() {
let result = decode_result("{not json");
assert!(result.is_err());
}
#[test]
fn decode_result_accepts_null() {
let decoded = decode_result("null").expect("valid JSON");
assert!(decoded.is_null());
}
#[test]
fn default_extensions_root_constant() {
assert_eq!(DEFAULT_EXTENSIONS_ROOT, "./extensions");
}
}