1use crate::core::{Promise, Value};
4use hara_abi::{Error, NativeModule, TaskEvent};
5use num_bigint::BigInt;
6use std::cell::RefCell;
7use std::collections::{BTreeMap, HashMap};
8use std::rc::Rc;
9use std::sync::Arc;
10
11#[derive(Clone, Default)]
12pub struct Registry {
13 modules: Rc<RefCell<HashMap<String, Arc<dyn NativeModule>>>>,
14}
15
16impl Registry {
17 pub fn install(&self, module: Arc<dyn NativeModule>) -> Result<(), String> {
18 let service = module.identity().export.clone();
19 let mut modules = self.modules.borrow_mut();
20 if modules.contains_key(&service) {
21 return Err(format!("native-module/duplicate: {service}"));
22 }
23 modules.insert(service, module);
24 Ok(())
25 }
26
27 pub fn services(&self) -> Vec<String> {
28 let mut services = self.modules.borrow().keys().cloned().collect::<Vec<_>>();
29 services.sort();
30 services
31 }
32
33 pub fn invoke(
34 &self,
35 service: String,
36 operation: String,
37 arguments: Vec<Value>,
38 ) -> Result<Value, String> {
39 let module = self
40 .modules
41 .borrow()
42 .get(&service)
43 .cloned()
44 .ok_or_else(|| format!("native-module/unavailable: {service}"))?;
45 if !module
46 .operations()
47 .iter()
48 .any(|candidate| *candidate == operation)
49 {
50 return Err(format!(
51 "native-module/operation-unknown: {service}/{operation}"
52 ));
53 }
54 let arguments = arguments
55 .iter()
56 .map(to_abi)
57 .collect::<Result<Vec<_>, _>>()?;
58 let task = module.start(&operation, arguments).map_err(error_message)?;
59 let promise = Promise::new();
60 install_hooks(&promise, module, task);
61 Ok(Value::Promise(promise))
62 }
63}
64
65fn install_hooks(promise: &Promise, module: Arc<dyn NativeModule>, task: u64) {
66 let destination = promise.clone();
67 let polling = module.clone();
68 promise.set_poller(Rc::new(move || {
69 settle(&destination, polling.as_ref(), task, polling.poll(task));
70 }));
71 let destination = promise.clone();
72 let waiting = module.clone();
73 promise.set_waiter(Rc::new(move || {
74 settle(
75 &destination,
76 waiting.as_ref(),
77 task,
78 waiting.wait(task, None),
79 );
80 }));
81 promise.set_cancel_hook(Rc::new(move || {
82 let _ = module.cancel(task);
83 module.drop_task(task);
84 }));
85}
86
87fn settle(
88 promise: &Promise,
89 module: &dyn NativeModule,
90 task: u64,
91 event: Result<TaskEvent, Error>,
92) {
93 match event {
94 Ok(TaskEvent::Pending) => {}
95 Ok(TaskEvent::Resolved(value)) => {
96 match from_abi(value) {
97 Ok(value) => {
98 promise.resolve(value);
99 }
100 Err(error) => {
101 promise.reject(error);
102 }
103 }
104 module.drop_task(task);
105 }
106 Ok(TaskEvent::Rejected(error)) | Err(error) => {
107 promise.reject(error_message(error));
108 module.drop_task(task);
109 }
110 }
111}
112
113fn error_message(error: Error) -> String {
114 format!("{}: {}", error.code, error.detail)
115}
116
117fn to_abi(value: &Value) -> Result<hara_abi::Value, String> {
118 use hara_abi::Value as Abi;
119 Ok(match value {
120 Value::Nil => Abi::Nil,
121 Value::Bool(value) => Abi::Boolean(*value),
122 Value::Number(value) => Abi::Integer(*value),
123 Value::BigInteger(value) => Abi::BigInteger(value.to_string()),
124 Value::Float(value) => Abi::Float(crate::numeric::finite_float(*value)?),
125 Value::String(value) => Abi::String(value.clone()),
126 Value::Bytes(value) => Abi::Bytes(value.clone()),
127 Value::ByteBuffer(value) => Abi::Bytes(value.borrow().clone()),
128 Value::Keyword(value) => Abi::Keyword(value.as_str().into()),
129 Value::Vector(values) => {
130 Abi::Vector(values.iter().map(to_abi).collect::<Result<Vec<_>, _>>()?)
131 }
132 Value::Tuple(values) => {
133 Abi::Vector(values.iter().map(to_abi).collect::<Result<Vec<_>, _>>()?)
134 }
135 Value::List(values) => {
136 Abi::Vector(values.iter().map(to_abi).collect::<Result<Vec<_>, _>>()?)
137 }
138 Value::Map(values) => {
139 let mut output = BTreeMap::new();
140 for (key, value) in values.iter() {
141 let key = match key {
142 Value::String(value) => value.clone(),
143 Value::Keyword(value) => value.as_str().into(),
144 _ => return Err("native-module/value-unsupported: record key".into()),
145 };
146 output.insert(key, to_abi(value)?);
147 }
148 Abi::Record(output)
149 }
150 _ => {
151 return Err(format!(
152 "native-module/value-unsupported: {}",
153 value.display()
154 ))
155 }
156 })
157}
158
159fn from_abi(value: hara_abi::Value) -> Result<Value, String> {
160 use hara_abi::Value as Abi;
161 Ok(match value {
162 Abi::Nil => Value::Nil,
163 Abi::Boolean(value) => Value::Bool(value),
164 Abi::Integer(value) => Value::Number(value),
165 Abi::BigInteger(value) => {
166 let value = BigInt::parse_bytes(value.as_bytes(), 10)
167 .ok_or_else(|| "native-module/value-invalid: big integer".to_string())?;
168 crate::numeric::compact_integer(value)
169 }
170 Abi::Float(value) => Value::Float(crate::numeric::finite_float(value)?),
171 Abi::String(value) => Value::String(value),
172 Abi::Bytes(value) => Value::Bytes(value),
173 Abi::Keyword(value) => Value::Keyword(value.into()),
174 Abi::Vector(values) => Value::Vector(
175 values
176 .into_iter()
177 .map(from_abi)
178 .collect::<Result<Vec<_>, _>>()?
179 .into(),
180 ),
181 Abi::Record(values) => Value::Map(
182 values
183 .into_iter()
184 .map(|(key, value)| Ok((Value::String(key), from_abi(value)?)))
185 .collect::<Result<Vec<_>, String>>()?
186 .into_iter()
187 .collect(),
188 ),
189 })
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use hara_abi::{NativeIdentity, TaskId};
196 use std::sync::Mutex;
197
198 struct Echo {
199 identity: NativeIdentity,
200 result: Mutex<Option<hara_abi::Value>>,
201 }
202
203 impl NativeModule for Echo {
204 fn identity(&self) -> &NativeIdentity {
205 &self.identity
206 }
207 fn operations(&self) -> &[&str] {
208 &["echo"]
209 }
210 fn capabilities(&self) -> &[&str] {
211 &[]
212 }
213 fn start(
214 &self,
215 _operation: &str,
216 mut arguments: Vec<hara_abi::Value>,
217 ) -> Result<TaskId, Error> {
218 *self.result.lock().unwrap() = arguments.pop();
219 Ok(1)
220 }
221 fn poll(&self, _task: TaskId) -> Result<TaskEvent, Error> {
222 Ok(self
223 .result
224 .lock()
225 .unwrap()
226 .take()
227 .map(TaskEvent::Resolved)
228 .unwrap_or(TaskEvent::Pending))
229 }
230 fn cancel(&self, _task: TaskId) -> Result<(), Error> {
231 Ok(())
232 }
233 fn drop_task(&self, _task: TaskId) {}
234 fn shutdown(&self) {}
235 }
236
237 #[test]
238 fn registry_returns_runtime_promises_without_leaking_module_values() {
239 let registry = Registry::default();
240 registry
241 .install(Arc::new(Echo {
242 identity: NativeIdentity::new("gh:example:echo", "test.echo", "echo", "test/1")
243 .unwrap(),
244 result: Mutex::new(None),
245 }))
246 .unwrap();
247 let Value::Promise(promise) = registry
248 .invoke(
249 "test.echo".into(),
250 "echo".into(),
251 vec![Value::BigInteger(
252 BigInt::parse_bytes(b"9223372036854775808", 10).unwrap(),
253 )],
254 )
255 .unwrap()
256 else {
257 panic!("promise")
258 };
259 assert!(matches!(
260 promise.state(),
261 crate::core::PromiseState::Fulfilled(Value::BigInteger(value))
262 if value == BigInt::parse_bytes(b"9223372036854775808", 10).unwrap()
263 ));
264 }
265}