use arora_types::call::Call;
use arora_types::value::{StructureField, Value};
use arora_types::value_serde;
use serde::de::DeserializeOwned;
use serde::Serialize;
use uuid::{uuid, Uuid};
use crate::graph::{Graph, GraphDiff};
use crate::task::{RunPolicy, TaskHandle, TaskId};
pub const ID: Uuid = uuid!("61726f72-6100-0000-0000-000000000001");
pub const EDIT: Uuid = uuid!("61726f72-6100-0000-0000-000000000002");
pub const EDIT_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000003");
pub const LOAD: Uuid = uuid!("61726f72-6100-0000-0000-000000000004");
pub const LOAD_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000005");
pub const SPAWN: Uuid = uuid!("61726f72-6100-0000-0000-000000000006");
pub const SPAWN_CALL_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000007");
pub const SPAWN_POLICY_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000008");
pub const HALT: Uuid = uuid!("61726f72-6100-0000-0000-000000000009");
pub const HALT_ARG: Uuid = uuid!("61726f72-6100-0000-0000-00000000000a");
pub fn encode_edit(diff: &GraphDiff) -> Call {
encode(EDIT, EDIT_ARG, diff)
}
pub fn decode_edit(call: &Call) -> Result<GraphDiff, String> {
decode(call, EDIT, EDIT_ARG, "GraphDiff")
}
pub fn encode_load(graph: &Graph) -> Call {
encode(LOAD, LOAD_ARG, graph)
}
pub fn decode_load(call: &Call) -> Result<Graph, String> {
decode(call, LOAD, LOAD_ARG, "Graph")
}
pub fn encode_spawn(call: &Call, policy: RunPolicy) -> Call {
Call {
module_id: Some(ID),
id: SPAWN,
args: vec![
StructureField {
id: SPAWN_CALL_ARG,
value: Box::new(value_serde::to_value(call).expect("a Call converts to a Value")),
},
StructureField {
id: SPAWN_POLICY_ARG,
value: Box::new(
value_serde::to_value(&policy).expect("a RunPolicy converts to a Value"),
),
},
],
}
}
pub fn decode_spawn(call: &Call) -> Result<(Call, RunPolicy), String> {
if call.id != SPAWN {
return Err(format!(
"not the expected interpreter function: {}",
call.id
));
}
let inner = decode_arg(call, SPAWN_CALL_ARG, "Call")?;
let policy = decode_arg(call, SPAWN_POLICY_ARG, "RunPolicy")?;
Ok((inner, policy))
}
pub fn encode_halt(task: TaskId) -> Call {
encode(HALT, HALT_ARG, &task)
}
pub fn decode_halt(call: &Call) -> Result<TaskId, String> {
decode(call, HALT, HALT_ARG, "TaskId")
}
pub fn encode_spawn_result(handle: &TaskHandle) -> Value {
value_serde::to_value(handle).expect("a TaskHandle converts to a Value")
}
pub fn decode_spawn_result(value: &Value) -> Result<TaskHandle, String> {
value_serde::from_value(value.clone()).map_err(|e| format!("malformed TaskHandle: {e}"))
}
fn encode<T: Serialize>(function: Uuid, arg: Uuid, payload: &T) -> Call {
let value = value_serde::to_value(payload).expect("a graph payload converts to a Value");
Call {
module_id: Some(ID),
id: function,
args: vec![StructureField {
id: arg,
value: Box::new(value),
}],
}
}
fn decode<T: DeserializeOwned>(
call: &Call,
function: Uuid,
arg: Uuid,
what: &str,
) -> Result<T, String> {
if call.id != function {
return Err(format!(
"not the expected interpreter function: {}",
call.id
));
}
decode_arg(call, arg, what)
}
fn decode_arg<T: DeserializeOwned>(call: &Call, arg: Uuid, what: &str) -> Result<T, String> {
let field = call
.args
.iter()
.find(|field| field.id == arg)
.ok_or_else(|| format!("the call is missing its {what} argument"))?;
value_serde::from_value(field.value.as_ref().clone())
.map_err(|e| format!("malformed {what}: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn edit_call_round_trips_the_diff() {
let diff = GraphDiff {
remove_nodes: vec![Uuid::from_u128(7)],
set_root: Some(Uuid::from_u128(9)),
..Default::default()
};
let call = encode_edit(&diff);
assert_eq!(call.module_id, Some(ID));
assert_eq!(call.id, EDIT);
assert_eq!(decode_edit(&call).unwrap(), diff);
}
#[test]
fn load_call_round_trips_the_graph() {
let mut graph = Graph::empty();
graph.root = Some(Uuid::from_u128(3));
let call = encode_load(&graph);
assert_eq!(call.module_id, Some(ID));
assert_eq!(call.id, LOAD);
assert_eq!(decode_load(&call).unwrap(), graph);
}
#[test]
fn spawn_call_round_trips_the_call_and_policy() {
let inner = Call {
module_id: Some(Uuid::from_u128(1)),
id: Uuid::from_u128(2),
args: vec![],
};
let call = encode_spawn(&inner, RunPolicy::Concurrent);
assert_eq!(call.module_id, Some(ID));
assert_eq!(call.id, SPAWN);
assert_eq!(decode_spawn(&call).unwrap(), (inner, RunPolicy::Concurrent));
let mut wrong = call;
wrong.id = Uuid::from_u128(1);
assert!(decode_spawn(&wrong).is_err());
}
#[test]
fn halt_call_round_trips_the_task_id() {
let task = TaskId(Uuid::from_u128(5));
let call = encode_halt(task);
assert_eq!(call.module_id, Some(ID));
assert_eq!(call.id, HALT);
assert_eq!(decode_halt(&call).unwrap(), task);
}
#[test]
fn spawn_result_round_trips_the_handle() {
use arora_types::data::Key;
let id = TaskId(Uuid::from_u128(5));
let handle = TaskHandle {
id,
stop: encode_halt(id),
status: Key::new("arora/tasks/m/f/5/status"),
feedback: vec![Key::new("arora/tasks/m/f/5/feedback")],
result: vec![Key::new("arora/tasks/m/f/5/result")],
update: vec![],
};
let value = encode_spawn_result(&handle);
assert_eq!(decode_spawn_result(&value).unwrap(), handle);
}
#[test]
fn decode_rejects_malformed_calls() {
let mut call = encode_edit(&GraphDiff::default());
call.id = Uuid::from_u128(1);
assert!(decode_edit(&call).is_err());
let mut call = encode_edit(&GraphDiff::default());
call.args.clear();
assert!(decode_edit(&call).is_err());
let mut call = encode_edit(&GraphDiff::default());
*call.args[0].value = arora_types::value::Value::Boolean(true);
assert!(decode_edit(&call).is_err());
let mut call = encode_load(&Graph::empty());
*call.args[0].value = arora_types::value::Value::String("not a graph".to_string());
assert!(decode_load(&call).is_err());
}
}