Skip to main content

arora_types/
call.rs

1use std::rc::Rc;
2
3use derive_more::Display;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::value::{StructureField, Value};
8
9/// A call is described like a structure in arora engine.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct Call {
12  /// The ID of the module where to find the function ID.
13  /// If absent, look for it locally.
14  #[serde(default)]
15  pub module_id: Option<Uuid>,
16  /// The function ID to call.
17  pub id: Uuid,
18  /// Arguments to call the functions with.
19  #[serde(default)]
20  pub args: Vec<StructureField>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24pub struct CallResult {
25  pub ret: Value,
26  #[serde(default)]
27  pub mutated: Vec<StructureField>,
28}
29
30/// Anything that can be invoked through a [`CallBridge`], e.g. a
31/// behavior-tree tick registered as an indirect callable.
32pub trait Callable {
33  fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, CallError>;
34}
35
36/// The interface a module uses to call back into its host (the engine, or a
37/// mock in tests). It lives here, in the interface layer, so module-shaped
38/// libraries can make host calls without depending on the engine crate.
39pub trait CallBridge {
40  /// Dispatch `call` to the module it names: the call is the full description
41  /// of the invocation, and one naming no module is refused.
42  fn arora_call(&mut self, call: Call) -> Result<CallResult, CallError>;
43
44  /// Registers the given function in the executor and associates it to an
45  /// identifier generated on the fly. The function is made available to
46  /// every module by calling `arora_dispatch_indirect(id: u64) -> Value`.
47  fn arora_register_callable(&mut self, callable: Rc<dyn Callable>) -> CallableId;
48
49  /// Unregisters the function associated to the given identifier.
50  fn arora_unregister_callable(&mut self, callable_id: &CallableId);
51
52  /// Calls a callable that was registered.
53  fn arora_call_indirect(&mut self, callable_id: &CallableId) -> Result<Value, CallError>;
54}
55
56#[derive(Display, Debug)]
57pub enum CallError {
58  Generic {
59    message: String,
60  },
61  ModuleNotFound {
62    id: Uuid,
63  },
64  FunctionNotFound {
65    id: Uuid,
66  },
67  Trap {
68    message: String,
69  },
70  Internal {
71    message: String,
72  },
73  /// The guest returned a structured error via TYPE_ERROR instead of trapping.
74  Guest {
75    message: String,
76  },
77}
78
79impl std::error::Error for CallError {}
80
81#[derive(Clone, Hash, Eq, PartialEq)]
82pub struct CallableId {
83  pub id: u64,
84}
85
86impl From<u64> for CallableId {
87  fn from(id: u64) -> Self {
88    Self { id }
89  }
90}
91
92impl Callable for CallableId {
93  fn call(&self, caller: &mut dyn CallBridge) -> Result<Value, CallError> {
94    caller.arora_call_indirect(self)
95  }
96}
97
98#[cfg(test)]
99mod tests {
100  use crate::value::{Structure, Value};
101
102  use super::*;
103  use std::str::FromStr;
104  use uuid::Uuid;
105
106  #[test]
107  pub fn parse_call_test() {
108    // The call format keeps values in their singleton-map YAML form.
109    let call: Call = serde_yaml::with::singleton_map_recursive::deserialize(
110      serde_yaml::Deserializer::from_str(CALL_TEST),
111    )
112    .unwrap();
113    assert_eq!(
114      call.id,
115      Uuid::from_str("07f5740c-ba4a-45af-8ec5-bedde5737e99").unwrap()
116    );
117    if let Value::Structure(Structure { id, fields }) = &call.args[1].value.as_ref() {
118      assert_eq!(
119        *id,
120        Uuid::from_str("7f9aedf8-dbde-4020-b5f4-c28a6635ae7c").unwrap()
121      );
122      if let Value::I32(v) = fields[1].value.as_ref() {
123        assert_eq!(*v, 113);
124      } else {
125        panic!("expected i32 value under second field of struct arg");
126      }
127    } else {
128      panic!("expected a string under arg 55dbec70-1c3a-433e-a6e6-27446b7f065e");
129    }
130  }
131
132  #[test]
133  pub fn parse_call_test_2() {
134    let call: Call = serde_yaml::with::singleton_map_recursive::deserialize(
135      serde_yaml::Deserializer::from_str(CALL_TEST_2),
136    )
137    .unwrap();
138    assert_eq!(
139      call.id,
140      Uuid::from_str("b213a552-77ad-465a-a26d-352e8eccfd63").unwrap()
141    );
142    assert_eq!(call.args.len(), 2);
143  }
144
145  /// Proves the call-bridge interface stands on its own: a module-shaped
146  /// library can register and invoke callables with no engine present.
147  #[test]
148  fn callable_round_trips_through_a_mock_bridge() {
149    use std::collections::HashMap;
150    use std::rc::Rc;
151
152    #[derive(Default)]
153    struct MockBridge {
154      registered: HashMap<CallableId, Rc<dyn Callable>>,
155      next_id: u64,
156    }
157
158    impl CallBridge for MockBridge {
159      fn arora_call(&mut self, _call: Call) -> Result<CallResult, CallError> {
160        Err(CallError::Generic {
161          message: "the mock has no modules".to_string(),
162        })
163      }
164      fn arora_register_callable(&mut self, callable: Rc<dyn Callable>) -> CallableId {
165        let id = CallableId::from(self.next_id);
166        self.next_id += 1;
167        self.registered.insert(id.clone(), callable);
168        id
169      }
170      fn arora_unregister_callable(&mut self, callable_id: &CallableId) {
171        self.registered.remove(callable_id);
172      }
173      fn arora_call_indirect(&mut self, callable_id: &CallableId) -> Result<Value, CallError> {
174        let callable = self
175          .registered
176          .get(callable_id)
177          .cloned()
178          .ok_or(CallError::Generic {
179            message: "unknown callable".to_string(),
180          })?;
181        callable.call(self)
182      }
183    }
184
185    struct Answer;
186    impl Callable for Answer {
187      fn call(&self, _caller: &mut dyn CallBridge) -> Result<Value, CallError> {
188        Ok(Value::I32(42))
189      }
190    }
191
192    let mut bridge = MockBridge::default();
193    let id = bridge.arora_register_callable(Rc::new(Answer));
194    let result = bridge.arora_call_indirect(&id).unwrap();
195    assert!(matches!(result, Value::I32(42)));
196
197    bridge.arora_unregister_callable(&id);
198    assert!(bridge.arora_call_indirect(&id).is_err());
199  }
200
201  pub const CALL_TEST: &str = "\
202id: 07f5740c-ba4a-45af-8ec5-bedde5737e99
203args:
204- id: b41899c3-66dc-40d4-ab61-d1ccf5231c88
205  value:
206    enum:
207      id: 325a5767-e344-4532-860e-0749bcf2e428
208      variant_id: 766e9e9a-446d-4e46-83e6-14b7ca101169
209      value: unit
210- id: 63086e48-804f-403a-8862-3358ddedc08d
211  value:
212    struct:
213      id: 7f9aedf8-dbde-4020-b5f4-c28a6635ae7c
214      fields:
215      - id: 7d94a956-e50d-4cc4-9714-f62e1f9b134e
216        value:
217          enums:
218            id: 325a5767-e344-4532-860e-0749bcf2e428
219            elements:
220              - variant_id: 2468f46c-bb60-425c-9a4d-9ad326ccc7e2
221                value: unit
222      - id: 5ffa9104-1e5c-4026-943f-8db38bd34563
223        value:
224          i32: 113
225";
226
227  pub const CALL_TEST_2: &str = "\
228id: b213a552-77ad-465a-a26d-352e8eccfd63
229args:
230- id: 55dbec70-1c3a-433e-a6e6-27446b7f065e
231  value:
232    u32: 42
233- id: abf9ca4e-e03f-431a-a32b-4911f809c399
234  value:
235    u32: 64
236";
237}