use std::error::Error;
use std::path::{Path, PathBuf};
use airsl::{Engine, GrantSet, HostModule, InstallContext, ModuleName, Policy, RootTable, Script};
#[derive(Debug)]
struct Metrics {
name: ModuleName,
}
impl Metrics {
fn new() -> Result<Self, airsl::Error> {
Ok(Self {
name: ModuleName::new("metrics")?,
})
}
}
impl HostModule for Metrics {
fn name(&self) -> &ModuleName {
&self.name
}
fn install(
&self,
lua: &airsl::mlua::Lua,
table: &airsl::mlua::Table,
context: &InstallContext<'_>,
) -> Result<(), airsl::Error> {
let fail = |e: airsl::mlua::Error| airsl::Error::lua("metrics", e);
let root = context.root_table().as_str().to_owned();
let may_reset = !context.grants().fs().write_roots().is_empty();
let granted = describe(context.grants());
let record = lua
.create_function(move |_, name: String| Ok(format!("{root}.metrics recorded {name}")))
.map_err(fail)?;
table.set("record", record).map_err(fail)?;
let reset = lua
.create_function(move |_, ()| {
if may_reset {
return Ok("metrics reset");
}
Err(airsl::mlua::Error::RuntimeError(format!(
"metrics.reset denied: it needs a filesystem write grant — granted: {granted}"
)))
})
.map_err(fail)?;
table.set("reset", reset).map_err(fail)?;
Ok(())
}
}
fn describe(grants: &GrantSet) -> String {
if grants.is_empty() {
String::from("none")
} else {
grants.to_string()
}
}
fn main() -> Result<(), Box<dyn Error>> {
let mut modules = airsl::modules::stdlib()?;
modules.insert(Box::new(Metrics::new()?))?;
let engine = Engine::builder()
.policy(Policy::confined())
.root_table(RootTable::new("myapp")?)
.stdlib(modules)
.build()?;
println!("root table: {}", engine.root_table().as_str());
println!(
"modules: {}",
engine
.module_names()
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
);
println!();
let path: PathBuf =
Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/custom-module/metrics.lua");
let script = Script::from_file(&path)?.with_name("metrics.lua")?;
engine.eval(&script)?;
Ok(())
}