use shape_runtime::json_value::JsonValue;
use shape_runtime::marshal::{register_typed_fn_1, register_typed_fn_2};
use shape_runtime::module_exports::ModuleExports;
use shape_runtime::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
use shape_wire::transport::Transport;
use shape_wire::transport::factory::TransportKind;
use shape_wire::WireValue;
use std::cell::RefCell;
use std::sync::Arc;
use super::transport_provider;
thread_local! {
static CURRENT_PROGRAM: RefCell<Option<crate::bytecode::BytecodeProgram>> = const { RefCell::new(None) };
}
pub fn set_current_program(program: &crate::bytecode::BytecodeProgram) {
CURRENT_PROGRAM.with(|p| {
*p.borrow_mut() = Some(program.clone());
});
}
pub fn clear_current_program() {
CURRENT_PROGRAM.with(|p| {
*p.borrow_mut() = None;
});
}
fn wire_roundtrip(
addr: &str,
msg: &crate::remote::WireMessage,
) -> Result<crate::remote::WireMessage, String> {
let transport: Arc<dyn Transport> = transport_provider::transport_provider()
.create_transport(TransportKind::Tcp)
.map_err(|e| format!("remote: failed to create transport: {}", e))?;
let mp =
shape_wire::encode_message(msg).map_err(|e| format!("remote: encode error: {}", e))?;
let response_bytes = transport
.send(addr, &mp)
.map_err(|e| format!("remote: transport error: {}", e))?;
shape_wire::decode_message(&response_bytes)
.map_err(|e| format!("remote: decode error: {}", e))
}
fn wire_to_json_value(wire: &WireValue) -> JsonValue {
match wire {
WireValue::Null => JsonValue::Null,
WireValue::Bool(b) => JsonValue::Bool(*b),
WireValue::Number(n) => JsonValue::Number(*n),
WireValue::Integer(i) => JsonValue::Int(*i),
WireValue::I8(v) => JsonValue::Int(*v as i64),
WireValue::I16(v) => JsonValue::Int(*v as i64),
WireValue::I32(v) => JsonValue::Int(*v as i64),
WireValue::I64(v) => JsonValue::Int(*v),
WireValue::U8(v) => JsonValue::Int(*v as i64),
WireValue::U16(v) => JsonValue::Int(*v as i64),
WireValue::U32(v) => JsonValue::Int(*v as i64),
WireValue::U64(v) => JsonValue::Int(*v as i64),
WireValue::Isize(v) => JsonValue::Int(*v),
WireValue::Usize(v) => JsonValue::Int(*v as i64),
WireValue::Ptr(v) => JsonValue::Int(*v as i64),
WireValue::F32(v) => JsonValue::Number(*v as f64),
WireValue::String(s) => JsonValue::String(s.clone()),
WireValue::Array(items) => {
JsonValue::Array(items.iter().map(wire_to_json_value).collect())
}
WireValue::Object(map) => JsonValue::Object(
map.iter()
.map(|(k, v)| (k.clone(), wire_to_json_value(v)))
.collect(),
),
WireValue::Result { ok, value } => {
let payload = wire_to_json_value(value);
let tag = if *ok { "Ok" } else { "Err" };
JsonValue::Object(vec![(tag.to_string(), payload)])
}
WireValue::Timestamp(ms) => JsonValue::Int(*ms),
WireValue::Duration { value, unit } => JsonValue::Object(vec![
("__duration".to_string(), JsonValue::Number(*value)),
("__unit".to_string(), JsonValue::String(format!("{:?}", unit))),
]),
WireValue::Table(_) => JsonValue::String("<wire:Table:phase-2c>".to_string()),
WireValue::Range { .. } => JsonValue::String("<wire:Range:phase-2c>".to_string()),
WireValue::FunctionRef { name } => {
JsonValue::String(format!("<wire:FunctionRef:{}>", name))
}
WireValue::PrintResult(_) => {
JsonValue::String("<wire:PrintResult:phase-2c>".to_string())
}
WireValue::Content(_) => JsonValue::String("<wire:Content:phase-2c>".to_string()),
}
}
pub fn create_remote_module() -> ModuleExports {
let mut module = ModuleExports::new("std::core::remote");
module.description = "Remote execution on Shape serve instances".to_string();
register_typed_fn_2::<_, Arc<String>, Arc<String>>(
&mut module,
"execute",
"Execute Shape code on a remote server",
[("addr", "string"), ("code", "string")],
ConcreteType::Named(
"Result<{ value, stdout: string?, error: string? }, string>".to_string(),
),
|addr, code, ctx| {
shape_runtime::module_exports::check_permission(
ctx,
shape_abi_v1::Permission::NetConnect,
)?;
let msg = crate::remote::WireMessage::Execute(crate::remote::ExecuteRequest {
code: (*code).clone(),
request_id: 1,
});
let response = match wire_roundtrip(addr.as_str(), &msg) {
Ok(r) => r,
Err(e) => {
return Ok(TypedReturn::Err(ConcreteReturn::String(format!(
"remote.execute(): {}",
e
))));
}
};
match response {
crate::remote::WireMessage::ExecuteResponse(r) => {
if r.success {
let value = ConcreteReturn::JsonValue(wire_to_json_value(&r.value));
let stdout = match r.stdout {
Some(s) => ConcreteReturn::String(s),
None => ConcreteReturn::String(String::new()),
};
let error = ConcreteReturn::String(String::new());
Ok(TypedReturn::OkObjectPairs(vec![
("value".to_string(), value),
("stdout".to_string(), stdout),
("error".to_string(), error),
]))
} else {
let msg = r.error.unwrap_or_else(|| "unknown error".to_string());
Ok(TypedReturn::Err(ConcreteReturn::String(msg)))
}
}
other => Ok(TypedReturn::Err(ConcreteReturn::String(format!(
"remote.execute(): unexpected response type: {:?}",
std::mem::discriminant(&other),
)))),
}
},
);
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"ping",
"Ping a remote Shape server and get server info",
"addr",
"string",
ConcreteType::Named(
"Result<{ shape_version: string, wire_protocol: int }, string>".to_string(),
),
|addr, ctx| {
shape_runtime::module_exports::check_permission(
ctx,
shape_abi_v1::Permission::NetConnect,
)?;
let msg = crate::remote::WireMessage::Ping(crate::remote::PingRequest {});
let response = match wire_roundtrip(addr.as_str(), &msg) {
Ok(r) => r,
Err(e) => {
return Ok(TypedReturn::Err(ConcreteReturn::String(format!(
"remote.ping(): {}",
e
))));
}
};
match response {
crate::remote::WireMessage::Pong(info) => {
Ok(TypedReturn::OkObjectPairs(vec![
(
"shape_version".to_string(),
ConcreteReturn::String(info.shape_version),
),
(
"wire_protocol".to_string(),
ConcreteReturn::I64(info.wire_protocol as i64),
),
]))
}
other => Ok(TypedReturn::Err(ConcreteReturn::String(format!(
"remote.ping(): unexpected response type: {:?}",
std::mem::discriminant(&other),
)))),
}
},
);
register_typed_fn_2::<_, Arc<String>, Arc<String>>(
&mut module,
"__call",
"Call a function on a remote Shape server",
[("addr", "string"), ("fn_name", "string")],
ConcreteType::Named("Result<_, string>".to_string()),
|_addr, _fn_name, ctx| {
shape_runtime::module_exports::check_permission(
ctx,
shape_abi_v1::Permission::NetConnect,
)?;
Ok(TypedReturn::Err(ConcreteReturn::String(
"remote.__call() requires upvalue kind track \
(ADR-006 §2.7.8 / Q10 cell-storage parallel-kind) — \
not yet wired through the typed-module-exports boundary; \
use remote.execute(addr, code) for source-level dispatch"
.to_string(),
)))
},
);
module
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remote_module_creation() {
let module = create_remote_module();
assert_eq!(module.name, "std::core::remote");
assert!(module.has_export("execute"));
assert!(module.has_export("ping"));
assert!(module.has_export("__call"));
}
#[test]
fn test_wire_to_json_primitives() {
assert_eq!(wire_to_json_value(&WireValue::Null), JsonValue::Null);
assert_eq!(
wire_to_json_value(&WireValue::Bool(true)),
JsonValue::Bool(true)
);
assert_eq!(
wire_to_json_value(&WireValue::Integer(42)),
JsonValue::Int(42)
);
assert_eq!(
wire_to_json_value(&WireValue::Number(3.14)),
JsonValue::Number(3.14)
);
assert_eq!(
wire_to_json_value(&WireValue::String("hello".to_string())),
JsonValue::String("hello".to_string())
);
}
#[test]
fn test_wire_to_json_structural() {
let arr = WireValue::Array(vec![
WireValue::Integer(1),
WireValue::Integer(2),
WireValue::Integer(3),
]);
match wire_to_json_value(&arr) {
JsonValue::Array(items) => {
assert_eq!(items.len(), 3);
assert_eq!(items[0], JsonValue::Int(1));
assert_eq!(items[2], JsonValue::Int(3));
}
other => panic!("expected JsonValue::Array, got {:?}", other),
}
}
#[test]
fn test_wire_result_to_json_tagged_object() {
let ok_wire = WireValue::Result {
ok: true,
value: Box::new(WireValue::Integer(42)),
};
match wire_to_json_value(&ok_wire) {
JsonValue::Object(pairs) => {
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0].0, "Ok");
assert_eq!(pairs[0].1, JsonValue::Int(42));
}
other => panic!("expected JsonValue::Object(Ok), got {:?}", other),
}
}
}