use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
use crate::value::{VmError, VmValue};
use super::turn_cache;
pub type HostCallDispatchFuture<'a> =
Pin<Box<dyn Future<Output = Result<Option<VmValue>, VmError>> + Send + 'a>>;
pub fn host_call_ready(
result: Result<Option<VmValue>, VmError>,
) -> HostCallDispatchFuture<'static> {
Box::pin(async move { result })
}
pub trait HostCallBridge: Send + Sync {
fn dispatch<'a>(
&'a self,
capability: &'a str,
operation: &'a str,
params: &'a crate::value::DictMap,
) -> HostCallDispatchFuture<'a>;
fn list_tools(&self) -> Result<Option<VmValue>, VmError> {
Ok(None)
}
fn call_tool(&self, _name: &str, _args: &VmValue) -> Result<Option<VmValue>, VmError> {
Ok(None)
}
}
thread_local! {
pub(super) static HOST_CALL_BRIDGE: RefCell<Option<Arc<dyn HostCallBridge>>> =
const { RefCell::new(None) };
}
pub fn set_host_call_bridge(bridge: Arc<dyn HostCallBridge>) {
turn_cache::reset();
HOST_CALL_BRIDGE.with(|b| *b.borrow_mut() = Some(bridge));
}
pub fn install_host_call_bridge(bridge: Arc<dyn HostCallBridge>) -> HostCallBridgeGuard {
turn_cache::reset();
let previous = HOST_CALL_BRIDGE.with(|slot| slot.borrow_mut().replace(bridge));
HostCallBridgeGuard {
previous,
_not_send: PhantomData,
}
}
#[must_use = "dropping the guard restores the previous host-call bridge"]
pub struct HostCallBridgeGuard {
previous: Option<Arc<dyn HostCallBridge>>,
_not_send: PhantomData<Rc<()>>,
}
impl Drop for HostCallBridgeGuard {
fn drop(&mut self) {
turn_cache::reset();
let previous = self.previous.take();
HOST_CALL_BRIDGE.with(|slot| *slot.borrow_mut() = previous);
}
}
pub fn clear_host_call_bridge() {
turn_cache::reset();
HOST_CALL_BRIDGE.with(|b| *b.borrow_mut() = None);
}
pub async fn dispatch_host_call_bridge(
capability: &str,
operation: &str,
params: &crate::value::DictMap,
) -> Option<Result<VmValue, VmError>> {
let bridge = HOST_CALL_BRIDGE.with(|b| b.borrow().clone())?;
match bridge.dispatch(capability, operation, params).await {
Ok(Some(value)) => Some(Ok(value)),
Ok(None) => None,
Err(error) => Some(Err(error)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::DictMap;
struct NamedBridge(&'static str);
impl HostCallBridge for NamedBridge {
fn dispatch<'a>(
&'a self,
_capability: &'a str,
_operation: &'a str,
_params: &'a DictMap,
) -> HostCallDispatchFuture<'a> {
host_call_ready(Ok(Some(VmValue::String(self.0.into()))))
}
}
async fn active_name() -> Option<String> {
dispatch_host_call_bridge("test", "name", &DictMap::new())
.await?
.ok()
.and_then(|value| match value {
VmValue::String(value) => Some(value.to_string()),
_ => None,
})
}
#[tokio::test(flavor = "current_thread")]
async fn lexical_bridge_install_restores_nested_state() {
clear_host_call_bridge();
let outer = install_host_call_bridge(Arc::new(NamedBridge("outer")));
assert_eq!(active_name().await.as_deref(), Some("outer"));
{
let _inner = install_host_call_bridge(Arc::new(NamedBridge("inner")));
assert_eq!(active_name().await.as_deref(), Some("inner"));
}
assert_eq!(active_name().await.as_deref(), Some("outer"));
drop(outer);
assert_eq!(active_name().await, None);
}
}