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