use std::collections::BTreeMap;
use std::sync::Arc;
use uni_plugin::Capability;
#[cfg(feature = "rhai-runtime")]
pub type RhaiHostFnRegister =
Arc<dyn Fn(&mut rhai::Engine, &uni_plugin::CapabilitySet) + Send + Sync + 'static>;
#[cfg(not(feature = "rhai-runtime"))]
pub type RhaiHostFnRegister = Arc<dyn Fn() + Send + Sync + 'static>;
#[derive(Clone)]
pub struct RhaiHostFnSpec {
pub name: String,
pub required_capability: Option<Capability>,
pub docs: String,
pub register: RhaiHostFnRegister,
}
#[cfg(feature = "rhai-runtime")]
impl RhaiHostFnSpec {
#[must_use]
pub fn gated(
name: impl Into<String>,
required_capability: Capability,
docs: impl Into<String>,
register: impl Fn(&mut rhai::Engine, &uni_plugin::CapabilitySet) + Send + Sync + 'static,
) -> Self {
Self {
name: name.into(),
required_capability: Some(required_capability),
docs: docs.into(),
register: Arc::new(register),
}
}
}
impl std::fmt::Debug for RhaiHostFnSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RhaiHostFnSpec")
.field("name", &self.name)
.field("required_capability", &self.required_capability)
.field("docs", &self.docs)
.finish_non_exhaustive()
}
}
#[derive(Debug, Default, Clone)]
pub struct RhaiHostFnRegistry {
specs: BTreeMap<String, RhaiHostFnSpec>,
}
impl RhaiHostFnRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, spec: RhaiHostFnSpec) {
self.specs.insert(spec.name.clone(), spec);
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&RhaiHostFnSpec> {
self.specs.get(name)
}
pub fn iter(&self) -> impl Iterator<Item = &RhaiHostFnSpec> {
self.specs.values()
}
#[must_use]
pub fn len(&self) -> usize {
self.specs.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.specs.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_starts_empty() {
let r = RhaiHostFnRegistry::new();
assert!(r.is_empty());
assert_eq!(r.len(), 0);
}
#[cfg(feature = "rhai-runtime")]
#[test]
fn register_and_lookup_round_trip() {
let mut r = RhaiHostFnRegistry::new();
r.register(RhaiHostFnSpec {
name: "uni.fs.read".to_owned(),
required_capability: Some(Capability::Filesystem {
read: vec!["/data/**".into()],
write: vec![],
}),
docs: "Read a file from the host filesystem.".to_owned(),
register: Arc::new(|_engine, _caps| { }),
});
let spec = r.get("uni.fs.read").expect("registered");
assert!(spec.required_capability.is_some());
assert_eq!(r.len(), 1);
}
#[cfg(feature = "rhai-runtime")]
#[test]
fn always_available_fns_have_no_required_capability() {
let mut r = RhaiHostFnRegistry::new();
r.register(RhaiHostFnSpec {
name: "uni.log".to_owned(),
required_capability: None,
docs: "Emit a tracing event.".to_owned(),
register: Arc::new(|_engine, _caps| {}),
});
let spec = r.get("uni.log").expect("registered");
assert!(spec.required_capability.is_none());
}
}