arora_behavior/
interpreter_module.rs1use arora_types::call::Call;
22use arora_types::value::StructureField;
23use arora_types::value_serde;
24use serde::de::DeserializeOwned;
25use serde::Serialize;
26use uuid::{uuid, Uuid};
27
28use crate::graph::{Graph, GraphDiff};
29
30pub const ID: Uuid = uuid!("61726f72-6100-0000-0000-000000000001");
34
35pub const EDIT: Uuid = uuid!("61726f72-6100-0000-0000-000000000002");
37
38pub const EDIT_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000003");
41
42pub const LOAD: Uuid = uuid!("61726f72-6100-0000-0000-000000000004");
44
45pub const LOAD_ARG: Uuid = uuid!("61726f72-6100-0000-0000-000000000005");
48
49pub fn encode_edit(diff: &GraphDiff) -> Call {
51 encode(EDIT, EDIT_ARG, diff)
52}
53
54pub fn decode_edit(call: &Call) -> Result<GraphDiff, String> {
56 decode(call, EDIT, EDIT_ARG, "GraphDiff")
57}
58
59pub fn encode_load(graph: &Graph) -> Call {
61 encode(LOAD, LOAD_ARG, graph)
62}
63
64pub fn decode_load(call: &Call) -> Result<Graph, String> {
66 decode(call, LOAD, LOAD_ARG, "Graph")
67}
68
69fn encode<T: Serialize>(function: Uuid, arg: Uuid, payload: &T) -> Call {
70 let value = value_serde::to_value(payload).expect("a graph payload converts to a Value");
71 Call {
72 module_id: Some(ID),
73 id: function,
74 args: vec![StructureField {
75 id: arg,
76 value: Box::new(value),
77 }],
78 }
79}
80
81fn decode<T: DeserializeOwned>(
82 call: &Call,
83 function: Uuid,
84 arg: Uuid,
85 what: &str,
86) -> Result<T, String> {
87 if call.id != function {
88 return Err(format!(
89 "not the expected interpreter function: {}",
90 call.id
91 ));
92 }
93 let field = call
94 .args
95 .iter()
96 .find(|field| field.id == arg)
97 .ok_or_else(|| format!("the call is missing its {what} argument"))?;
98 value_serde::from_value(field.value.as_ref().clone())
99 .map_err(|e| format!("malformed {what}: {e}"))
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn edit_call_round_trips_the_diff() {
108 let diff = GraphDiff {
109 remove_nodes: vec![Uuid::from_u128(7)],
110 set_root: Some(Uuid::from_u128(9)),
111 ..Default::default()
112 };
113 let call = encode_edit(&diff);
114 assert_eq!(call.module_id, Some(ID));
115 assert_eq!(call.id, EDIT);
116 assert_eq!(decode_edit(&call).unwrap(), diff);
117 }
118
119 #[test]
120 fn load_call_round_trips_the_graph() {
121 let mut graph = Graph::empty();
122 graph.root = Some(Uuid::from_u128(3));
123 let call = encode_load(&graph);
124 assert_eq!(call.module_id, Some(ID));
125 assert_eq!(call.id, LOAD);
126 assert_eq!(decode_load(&call).unwrap(), graph);
127 }
128
129 #[test]
130 fn decode_rejects_malformed_calls() {
131 let mut call = encode_edit(&GraphDiff::default());
133 call.id = Uuid::from_u128(1);
134 assert!(decode_edit(&call).is_err());
135
136 let mut call = encode_edit(&GraphDiff::default());
138 call.args.clear();
139 assert!(decode_edit(&call).is_err());
140
141 let mut call = encode_edit(&GraphDiff::default());
143 call.args[0].value = Box::new(arora_types::value::Value::Boolean(true));
144 assert!(decode_edit(&call).is_err());
145 let mut call = encode_load(&Graph::empty());
146 call.args[0].value = Box::new(arora_types::value::Value::String("not a graph".to_string()));
147 assert!(decode_load(&call).is_err());
148 }
149}