harn_vm/stdlib/host/
bridge.rs1use std::cell::RefCell;
4use std::future::Future;
5use std::marker::PhantomData;
6use std::pin::Pin;
7use std::rc::Rc;
8use std::sync::Arc;
9
10use crate::value::{DictMap, VmError, VmValue};
11
12use super::turn_cache;
13
14pub type HostCallDispatchFuture<'a> =
23 Pin<Box<dyn Future<Output = Result<Option<VmValue>, VmError>> + Send + 'a>>;
24
25pub fn host_call_ready(
27 result: Result<Option<VmValue>, VmError>,
28) -> HostCallDispatchFuture<'static> {
29 Box::pin(async move { result })
30}
31
32pub(super) async fn dispatch_cached(
36 bridge: Arc<dyn HostCallBridge>,
37 capability: &str,
38 operation: &str,
39 params: &DictMap,
40) -> Result<Option<VmValue>, VmError> {
41 if capability == "project" && operation == "metadata_get" {
42 return turn_cache::cached_metadata_or(params, |params: DictMap| async move {
43 bridge.dispatch(capability, operation, ¶ms).await
44 })
45 .await;
46 }
47 turn_cache::cached_or(capability, operation, params, || {
48 bridge.dispatch(capability, operation, params)
49 })
50 .await
51}
52
53pub trait HostCallBridge: Send + Sync {
66 fn dispatch<'a>(
67 &'a self,
68 capability: &'a str,
69 operation: &'a str,
70 params: &'a crate::value::DictMap,
71 ) -> HostCallDispatchFuture<'a>;
72
73 fn list_tools(&self) -> Result<Option<VmValue>, VmError> {
74 Ok(None)
75 }
76
77 fn call_tool(&self, _name: &str, _args: &VmValue) -> Result<Option<VmValue>, VmError> {
78 Ok(None)
79 }
80}
81
82thread_local! {
83 pub(super) static HOST_CALL_BRIDGE: RefCell<Option<Arc<dyn HostCallBridge>>> =
84 const { RefCell::new(None) };
85}
86
87pub fn set_host_call_bridge(bridge: Arc<dyn HostCallBridge>) {
92 turn_cache::reset();
93 HOST_CALL_BRIDGE.with(|b| *b.borrow_mut() = Some(bridge));
94}
95
96pub fn install_host_call_bridge(bridge: Arc<dyn HostCallBridge>) -> HostCallBridgeGuard {
98 turn_cache::reset();
99 let previous = HOST_CALL_BRIDGE.with(|slot| slot.borrow_mut().replace(bridge));
100 HostCallBridgeGuard {
101 previous,
102 _not_send: PhantomData,
103 }
104}
105
106#[must_use = "dropping the guard restores the previous host-call bridge"]
107pub struct HostCallBridgeGuard {
108 previous: Option<Arc<dyn HostCallBridge>>,
109 _not_send: PhantomData<Rc<()>>,
112}
113
114impl Drop for HostCallBridgeGuard {
115 fn drop(&mut self) {
116 turn_cache::reset();
117 let previous = self.previous.take();
118 HOST_CALL_BRIDGE.with(|slot| *slot.borrow_mut() = previous);
119 }
120}
121
122pub fn clear_host_call_bridge() {
124 turn_cache::reset();
125 HOST_CALL_BRIDGE.with(|b| *b.borrow_mut() = None);
126}
127
128pub async fn dispatch_host_call_bridge(
138 capability: &str,
139 operation: &str,
140 params: &crate::value::DictMap,
141) -> Option<Result<VmValue, VmError>> {
142 let bridge = HOST_CALL_BRIDGE.with(|b| b.borrow().clone())?;
143 match bridge.dispatch(capability, operation, params).await {
144 Ok(Some(value)) => Some(Ok(value)),
145 Ok(None) => None,
146 Err(error) => Some(Err(error)),
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::value::DictMap;
154
155 struct NamedBridge(&'static str);
156
157 impl HostCallBridge for NamedBridge {
158 fn dispatch<'a>(
159 &'a self,
160 _capability: &'a str,
161 _operation: &'a str,
162 _params: &'a DictMap,
163 ) -> HostCallDispatchFuture<'a> {
164 host_call_ready(Ok(Some(VmValue::String(self.0.into()))))
165 }
166 }
167
168 async fn active_name() -> Option<String> {
169 dispatch_host_call_bridge("test", "name", &DictMap::new())
170 .await?
171 .ok()
172 .and_then(|value| match value {
173 VmValue::String(value) => Some(value.to_string()),
174 _ => None,
175 })
176 }
177
178 #[tokio::test(flavor = "current_thread")]
179 async fn lexical_bridge_install_restores_nested_state() {
180 clear_host_call_bridge();
181 let outer = install_host_call_bridge(Arc::new(NamedBridge("outer")));
182 assert_eq!(active_name().await.as_deref(), Some("outer"));
183 {
184 let _inner = install_host_call_bridge(Arc::new(NamedBridge("inner")));
185 assert_eq!(active_name().await.as_deref(), Some("inner"));
186 }
187 assert_eq!(active_name().await.as_deref(), Some("outer"));
188 drop(outer);
189 assert_eq!(active_name().await, None);
190 }
191}