use mlua::prelude::*;
use serde_json::Value as JsonValue;
use serde_yaml2::wrapper::YamlNodeWrapper as YamlValue;
use toml::Value as TomlValue;
const LUA_SERIALIZE_OPTIONS: LuaSerializeOptions = LuaSerializeOptions::new()
.set_array_metatable(false)
.serialize_none_to_null(false)
.serialize_unit_to_null(false);
const LUA_DESERIALIZE_OPTIONS: LuaDeserializeOptions = LuaDeserializeOptions::new()
.sort_keys(true)
.deny_recursive_tables(false)
.deny_unsupported_types(true);
#[derive(Debug, Clone, Copy)]
pub enum EncodeDecodeFormat {
Json,
JsonC,
Yaml,
Toml,
}
impl FromLua for EncodeDecodeFormat {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let LuaValue::String(s) = &value {
match s.to_string_lossy().to_ascii_lowercase().trim() {
"json" => Ok(Self::Json),
"jsonc" => Ok(Self::JsonC),
"yaml" => Ok(Self::Yaml),
"toml" => Ok(Self::Toml),
kind => Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "EncodeDecodeFormat".to_string(),
message: Some(format!(
"Invalid format '{kind}', valid formats are: json, yaml, toml"
)),
}),
}
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "EncodeDecodeFormat".to_string(),
message: None,
})
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct EncodeDecodeConfig {
pub format: EncodeDecodeFormat,
pub pretty: bool,
}
impl From<EncodeDecodeFormat> for EncodeDecodeConfig {
fn from(format: EncodeDecodeFormat) -> Self {
Self {
format,
pretty: false,
}
}
}
impl From<(EncodeDecodeFormat, bool)> for EncodeDecodeConfig {
fn from(value: (EncodeDecodeFormat, bool)) -> Self {
Self {
format: value.0,
pretty: value.1,
}
}
}
pub fn encode(value: LuaValue, lua: &Lua, config: EncodeDecodeConfig) -> LuaResult<LuaString> {
let bytes = match config.format {
EncodeDecodeFormat::Json | EncodeDecodeFormat::JsonC => {
let serialized: JsonValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
if config.pretty {
serde_json::to_vec_pretty(&serialized).into_lua_err()?
} else {
serde_json::to_vec(&serialized).into_lua_err()?
}
}
EncodeDecodeFormat::Yaml => {
let serialized: YamlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
serde_yaml2::to_string(serialized)
.into_lua_err()?
.into_bytes()
}
EncodeDecodeFormat::Toml => {
let serialized: TomlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
let s = if config.pretty {
toml::to_string_pretty(&serialized).into_lua_err()?
} else {
toml::to_string(&serialized).into_lua_err()?
};
s.as_bytes().to_vec()
}
};
lua.create_string(bytes)
}
pub fn decode(
bytes: impl AsRef<[u8]>,
lua: &Lua,
config: EncodeDecodeConfig,
) -> LuaResult<LuaValue> {
let bytes = bytes.as_ref();
match config.format {
EncodeDecodeFormat::Json => {
let value: JsonValue = serde_json::from_slice(bytes).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
}
EncodeDecodeFormat::JsonC => {
let string: String = String::from_utf8(bytes.to_vec()).into_lua_err()?;
let value: JsonValue =
jsonc_parser::parse_to_serde_value(&string, &jsonc_parser::ParseOptions::default())
.map(|v| v.unwrap_or(JsonValue::Null))
.into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
}
EncodeDecodeFormat::Yaml => {
let string: String = String::from_utf8(bytes.to_vec()).into_lua_err()?;
let value: YamlValue = serde_yaml2::from_str(&string).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
}
EncodeDecodeFormat::Toml => {
if let Ok(s) = String::from_utf8(bytes.to_vec()) {
let value: TomlValue = toml::from_str(&s).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
} else {
Err(LuaError::RuntimeError(
"TOML must be valid utf-8".to_string(),
))
}
}
}
}