use std::collections::HashMap;
use std::path::Path;
use mlua::{Lua, LuaOptions, LuaSerdeExt, StdLib, Value};
use serde::Deserialize;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ManifestError {
#[error("manifest {path}: lua error: {source}")]
Lua {
path: String,
#[source]
source: mlua::Error,
},
#[error("manifest {path}: top-level value is {got}, expected a table")]
NotATable { path: String, got: String },
#[error("manifest {path}: failed to decode: {source}")]
Decode {
path: String,
#[source]
source: mlua::Error,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Manifest {
pub assay: Option<String>,
#[serde(default)]
pub extensions: Vec<Extension>,
#[serde(default)]
pub libs: Vec<Lib>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Extension {
pub name: String,
pub version: String,
pub sha256: HashMap<String, String>,
#[serde(default)]
pub source: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Lib {
pub name: String,
pub version: String,
pub sha256: String,
#[serde(default)]
pub source: Option<String>,
}
pub fn parse(source: &str, path: impl AsRef<Path>) -> Result<Manifest, ManifestError> {
let path_str = path.as_ref().display().to_string();
let lua = sandboxed_vm().map_err(|e| ManifestError::Lua {
path: path_str.clone(),
source: e,
})?;
let chunk = lua.load(source).set_name(path_str.clone());
let value: Value = chunk.eval().map_err(|e| ManifestError::Lua {
path: path_str.clone(),
source: e,
})?;
if !matches!(value, Value::Table(_)) {
return Err(ManifestError::NotATable {
path: path_str,
got: value.type_name().to_string(),
});
}
lua.from_value::<Manifest>(value)
.map_err(|e| ManifestError::Decode {
path: path_str,
source: e,
})
}
fn sandboxed_vm() -> mlua::Result<Lua> {
let libs = StdLib::STRING | StdLib::TABLE | StdLib::MATH;
let lua = Lua::new_with(libs, LuaOptions::default())?;
let globals = lua.globals();
for name in ["load", "loadfile", "loadstring", "dofile", "require"] {
globals.set(name, mlua::Nil)?;
}
Ok(lua)
}