1use arora_types::call::Call;
26use arora_types::value::{StructureField, Value};
27use arora_types::value_serde;
28use serde::de::DeserializeOwned;
29use serde::Serialize;
30use uuid::{uuid, Uuid};
31
32use crate::graph::{Graph, GraphDiff};
33use crate::task::{RunPolicy, TaskHandle, TaskId};
34
35pub const ID: Uuid = uuid!("61726f72-6100-0000-0000-000000000001");
39
40pub const EDIT: Uuid = uuid!("61726f72-6100-0000-0000-000000000002");
42
43pub const EDIT_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000003");
46
47pub const LOAD: Uuid = uuid!("61726f72-6100-0000-0000-000000000004");
49
50pub const LOAD_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000005");
53
54pub const SPAWN: Uuid = uuid!("61726f72-6100-0000-0000-000000000006");
57
58pub const SPAWN_CALL_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000007");
61
62pub const SPAWN_POLICY_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000008");
64
65pub const HALT: Uuid = uuid!("61726f72-6100-0000-0000-000000000009");
67
68pub const HALT_ARG: Uuid = uuid!("61726f72-6100-0000-0000-00000000000a");
70
71pub fn encode_edit(diff: &GraphDiff) -> Call {
73 encode(EDIT, EDIT_ARG, diff)
74}
75
76pub fn decode_edit(call: &Call) -> Result<GraphDiff, String> {
78 decode(call, EDIT, EDIT_ARG, "GraphDiff")
79}
80
81pub fn encode_load(graph: &Graph) -> Call {
83 encode(LOAD, LOAD_ARG, graph)
84}
85
86pub fn decode_load(call: &Call) -> Result<Graph, String> {
88 decode(call, LOAD, LOAD_ARG, "Graph")
89}
90
91pub fn encode_spawn(call: &Call, policy: RunPolicy) -> Call {
93 Call {
94 module_id: Some(ID),
95 id: SPAWN,
96 args: vec![
97 StructureField {
98 id: SPAWN_CALL_ARG,
99 value: Box::new(value_serde::to_value(call).expect("a Call converts to a Value")),
100 },
101 StructureField {
102 id: SPAWN_POLICY_ARG,
103 value: Box::new(
104 value_serde::to_value(&policy).expect("a RunPolicy converts to a Value"),
105 ),
106 },
107 ],
108 }
109}
110
111pub fn decode_spawn(call: &Call) -> Result<(Call, RunPolicy), String> {
113 if call.id != SPAWN {
114 return Err(format!(
115 "not the expected interpreter function: {}",
116 call.id
117 ));
118 }
119 let inner = decode_arg(call, SPAWN_CALL_ARG, "Call")?;
120 let policy = decode_arg(call, SPAWN_POLICY_ARG, "RunPolicy")?;
121 Ok((inner, policy))
122}
123
124pub fn encode_halt(task: TaskId) -> Call {
126 encode(HALT, HALT_ARG, &task)
127}
128
129pub fn decode_halt(call: &Call) -> Result<TaskId, String> {
131 decode(call, HALT, HALT_ARG, "TaskId")
132}
133
134pub fn encode_spawn_result(handle: &TaskHandle) -> Value {
137 value_serde::to_value(handle).expect("a TaskHandle converts to a Value")
138}
139
140pub fn decode_spawn_result(value: &Value) -> Result<TaskHandle, String> {
142 value_serde::from_value(value.clone()).map_err(|e| format!("malformed TaskHandle: {e}"))
143}
144
145fn encode<T: Serialize>(function: Uuid, arg: Uuid, payload: &T) -> Call {
146 let value = value_serde::to_value(payload).expect("a graph payload converts to a Value");
147 Call {
148 module_id: Some(ID),
149 id: function,
150 args: vec![StructureField {
151 id: arg,
152 value: Box::new(value),
153 }],
154 }
155}
156
157fn decode<T: DeserializeOwned>(
158 call: &Call,
159 function: Uuid,
160 arg: Uuid,
161 what: &str,
162) -> Result<T, String> {
163 if call.id != function {
164 return Err(format!(
165 "not the expected interpreter function: {}",
166 call.id
167 ));
168 }
169 decode_arg(call, arg, what)
170}
171
172fn decode_arg<T: DeserializeOwned>(call: &Call, arg: Uuid, what: &str) -> Result<T, String> {
175 let field = call
176 .args
177 .iter()
178 .find(|field| field.id == arg)
179 .ok_or_else(|| format!("the call is missing its {what} argument"))?;
180 value_serde::from_value(field.value.as_ref().clone())
181 .map_err(|e| format!("malformed {what}: {e}"))
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn edit_call_round_trips_the_diff() {
190 let diff = GraphDiff {
191 remove_nodes: vec![Uuid::from_u128(7)],
192 set_root: Some(Uuid::from_u128(9)),
193 ..Default::default()
194 };
195 let call = encode_edit(&diff);
196 assert_eq!(call.module_id, Some(ID));
197 assert_eq!(call.id, EDIT);
198 assert_eq!(decode_edit(&call).unwrap(), diff);
199 }
200
201 #[test]
202 fn load_call_round_trips_the_graph() {
203 let mut graph = Graph::empty();
204 graph.root = Some(Uuid::from_u128(3));
205 let call = encode_load(&graph);
206 assert_eq!(call.module_id, Some(ID));
207 assert_eq!(call.id, LOAD);
208 assert_eq!(decode_load(&call).unwrap(), graph);
209 }
210
211 #[test]
212 fn spawn_call_round_trips_the_call_and_policy() {
213 let inner = Call {
214 module_id: Some(Uuid::from_u128(1)),
215 id: Uuid::from_u128(2),
216 args: vec![],
217 };
218 let call = encode_spawn(&inner, RunPolicy::Concurrent);
219 assert_eq!(call.module_id, Some(ID));
220 assert_eq!(call.id, SPAWN);
221 assert_eq!(decode_spawn(&call).unwrap(), (inner, RunPolicy::Concurrent));
222
223 let mut wrong = call;
225 wrong.id = Uuid::from_u128(1);
226 assert!(decode_spawn(&wrong).is_err());
227 }
228
229 #[test]
230 fn halt_call_round_trips_the_task_id() {
231 let task = TaskId(Uuid::from_u128(5));
232 let call = encode_halt(task);
233 assert_eq!(call.module_id, Some(ID));
234 assert_eq!(call.id, HALT);
235 assert_eq!(decode_halt(&call).unwrap(), task);
236 }
237
238 #[test]
239 fn spawn_result_round_trips_the_handle() {
240 use arora_types::data::Key;
241 let id = TaskId(Uuid::from_u128(5));
242 let handle = TaskHandle {
243 id,
244 stop: encode_halt(id),
245 status: Key::new("arora/tasks/m/f/5/status"),
246 feedback: vec![Key::new("arora/tasks/m/f/5/feedback")],
247 result: vec![Key::new("arora/tasks/m/f/5/result")],
248 update: vec![],
249 };
250 let value = encode_spawn_result(&handle);
251 assert_eq!(decode_spawn_result(&value).unwrap(), handle);
252 }
253
254 #[test]
255 fn decode_rejects_malformed_calls() {
256 let mut call = encode_edit(&GraphDiff::default());
258 call.id = Uuid::from_u128(1);
259 assert!(decode_edit(&call).is_err());
260
261 let mut call = encode_edit(&GraphDiff::default());
263 call.args.clear();
264 assert!(decode_edit(&call).is_err());
265
266 let mut call = encode_edit(&GraphDiff::default());
268 *call.args[0].value = arora_types::value::Value::Boolean(true);
269 assert!(decode_edit(&call).is_err());
270 let mut call = encode_load(&Graph::empty());
271 *call.args[0].value = arora_types::value::Value::String("not a graph".to_string());
272 assert!(decode_load(&call).is_err());
273 }
274}