pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("lua error in {chunk}: {source}")]
Lua {
chunk: String,
#[source]
source: Box<mlua::Error>,
},
#[error("cannot configure the Lua state ({stage}): {source}")]
EngineSetup {
stage: &'static str,
#[source]
source: Box<mlua::Error>,
},
#[error("script `{chunk}` exceeded its memory limit of {limit} bytes")]
MemoryLimit {
chunk: String,
limit: usize,
#[source]
source: Box<mlua::Error>,
},
#[error("script `{chunk}` exceeded its instruction limit of {limit}")]
InstructionLimit {
chunk: String,
limit: u64,
},
#[error("failed to install host module `{module}`: {reason}")]
ModuleInstall {
module: String,
reason: String,
},
#[error("host module `{module}` is already registered")]
DuplicateModule {
module: String,
},
#[error("invalid {kind} `{value}`: {reason}")]
InvalidName {
kind: &'static str,
value: String,
reason: &'static str,
},
#[error("cannot read script `{path}`: {source}")]
ScriptRead {
path: String,
#[source]
source: std::io::Error,
},
#[error("module `{module}` resolves outside the script directory `{root}`")]
RequireEscape {
module: String,
root: String,
},
#[error("module `{module}` requires itself, directly or indirectly, under `{root}`")]
RequireCycle {
module: String,
root: String,
},
#[error("{module}.{operation} denied: {detail}")]
Denied {
module: &'static str,
operation: &'static str,
detail: String,
},
#[error("{operation} failed on `{path}`: {source}")]
Io {
operation: &'static str,
path: String,
#[source]
source: std::io::Error,
},
#[error("cannot resolve `{path}` to check it against the policy: {reason}")]
UncheckablePath {
path: String,
reason: &'static str,
},
#[error("path `{path}` is outside `{base}`")]
PathNotRelative {
path: String,
base: String,
},
#[error("cannot resolve path `{path}`: {source}")]
PathResolution {
path: String,
#[source]
source: std::io::Error,
},
#[error("module `{module}` not found under `{root}`")]
RequireNotFound {
module: String,
root: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExhaustedLimit {
Memory,
Instructions,
}
impl Error {
#[must_use]
pub fn lua(chunk: impl Into<String>, source: mlua::Error) -> Self {
Self::Lua {
chunk: chunk.into(),
source: Box::new(source),
}
}
#[must_use]
pub const fn exhausted_limit(&self) -> Option<ExhaustedLimit> {
match self {
Self::MemoryLimit { .. } => Some(ExhaustedLimit::Memory),
Self::InstructionLimit { .. } => Some(ExhaustedLimit::Instructions),
_ => None,
}
}
}
impl From<Error> for mlua::Error {
fn from(value: Error) -> Self {
Self::external(value)
}
}
#[cfg(test)]
mod tests {
use super::Error;
#[test]
fn lua_wraps_chunk_name_into_the_message() {
let err = Error::lua("enforce.lua", mlua::Error::RuntimeError("boom".into()));
assert!(err.to_string().starts_with("lua error in enforce.lua: "));
}
#[test]
fn require_escape_names_both_module_and_root() {
let err = Error::RequireEscape {
module: "../secrets".into(),
root: "/plugins".into(),
};
assert_eq!(
err.to_string(),
"module `../secrets` resolves outside the script directory `/plugins`"
);
}
#[test]
fn converting_into_an_mlua_error_preserves_the_message() {
let err = Error::DuplicateModule {
module: "fs".into(),
};
let text = err.to_string();
let lua: mlua::Error = err.into();
assert!(lua.to_string().contains(&text));
}
}