pub trait TealRecord {
const NAME: &'static str;
const DECL: &'static str;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldError {
pub path: String,
pub expected: String,
pub got: String,
}
impl std::fmt::Display for FieldError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}: expected {}, got {}",
self.path, self.expected, self.got
)
}
}
impl std::error::Error for FieldError {}
pub fn field_error(
record: &str,
field: &str,
expected: &str,
got: &str,
cause: mlua::Error,
) -> mlua::Error {
located(format!("{record}.{field}"), expected, got, cause)
}
pub fn value_error(name: &str, expected: &str, got: &str, cause: mlua::Error) -> mlua::Error {
located(name.to_string(), expected, got, cause)
}
fn located(path: String, expected: &str, got: &str, cause: mlua::Error) -> mlua::Error {
if let Some(inner) = cause.downcast_ref::<FieldError>() {
let path = match inner.path.split_once('.') {
Some((_, rest)) => format!("{path}.{rest}"),
None => path,
};
return mlua::Error::external(FieldError {
path,
expected: inner.expected.clone(),
got: inner.got.clone(),
});
}
if !matches!(cause, mlua::Error::FromLuaConversionError { .. }) {
return mlua::ErrorContext::context(cause, path);
}
mlua::Error::external(FieldError {
path,
expected: expected.to_string(),
got: got.to_string(),
})
}
pub fn enum_error(name: &str, variants: &[&str], got: &str) -> mlua::Error {
let list: Vec<String> = variants.iter().map(|v| format!("\"{v}\"")).collect();
mlua::Error::external(FieldError {
path: name.to_string(),
expected: format!("one of {}", list.join(", ")),
got: got.to_string(),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Strict<T>(pub T);
impl<T> Strict<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> std::ops::Deref for Strict<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for Strict<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> From<T> for Strict<T> {
fn from(v: T) -> Self {
Strict(v)
}
}
pub trait StrictKind: Sized + private::Sealed {
const TEAL: &'static str;
fn from_value(v: &mlua::Value) -> Option<Self>;
}
mod private {
pub trait Sealed {}
}
macro_rules! strict_int {
($($t:ty),*) => {$(
impl private::Sealed for $t {}
impl StrictKind for $t {
const TEAL: &'static str = "integer";
fn from_value(v: &mlua::Value) -> Option<Self> {
match *v {
mlua::Value::Integer(i) => <$t>::try_from(i).ok(),
mlua::Value::Number(n) if n.fract() == 0.0 && n.is_finite() => {
let i = n as i64;
(i as f64 == n).then(|| <$t>::try_from(i).ok()).flatten()
}
_ => None,
}
}
}
)*};
}
strict_int!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
);
macro_rules! strict_float {
($($t:ty),*) => {$(
impl private::Sealed for $t {}
impl StrictKind for $t {
const TEAL: &'static str = "number";
fn from_value(v: &mlua::Value) -> Option<Self> {
match *v {
mlua::Value::Integer(i) => Some(i as $t),
mlua::Value::Number(n) => Some(n as $t),
_ => None,
}
}
}
)*};
}
strict_float!(f32, f64);
impl private::Sealed for bool {}
impl StrictKind for bool {
const TEAL: &'static str = "boolean";
fn from_value(v: &mlua::Value) -> Option<Self> {
match *v {
mlua::Value::Boolean(b) => Some(b),
_ => None,
}
}
}
impl private::Sealed for String {}
impl StrictKind for String {
const TEAL: &'static str = "string";
fn from_value(v: &mlua::Value) -> Option<Self> {
match v {
mlua::Value::String(s) => s.to_str().ok().map(|s| s.to_owned()),
_ => None,
}
}
}
impl<T: StrictKind> mlua::FromLua for Strict<T> {
fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> mlua::Result<Self> {
T::from_value(&value)
.map(Strict)
.ok_or_else(|| mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: T::TEAL.to_string(),
message: Some(format!(
"a Strict parameter takes a Lua {} and converts nothing",
T::TEAL
)),
})
}
}
impl<T: mlua::IntoLua> mlua::IntoLua for Strict<T> {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
self.0.into_lua(lua)
}
}
pub trait HostModule {
const MODULE: &'static str;
const DECL: &'static str;
}