Skip to main content

hara_native/
native_module.rs

1//! Runtime adapter for publication-linked `hara-abi` native modules.
2
3use 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}