mirror of
https://github.com/lune-org/lune.git
synced 2024-12-13 05:20:37 +00:00
Add back serde builtin
This commit is contained in:
parent
d6dbe5c861
commit
780f155377
4 changed files with 350 additions and 0 deletions
|
@ -3,6 +3,7 @@ use std::str::FromStr;
|
|||
use mlua::prelude::*;
|
||||
|
||||
mod luau;
|
||||
mod serde;
|
||||
mod stdio;
|
||||
mod task;
|
||||
|
||||
|
@ -10,6 +11,7 @@ mod task;
|
|||
pub enum LuneBuiltin {
|
||||
Luau,
|
||||
Task,
|
||||
Serde,
|
||||
Stdio,
|
||||
}
|
||||
|
||||
|
@ -21,6 +23,7 @@ where
|
|||
match self {
|
||||
Self::Luau => "luau",
|
||||
Self::Task => "task",
|
||||
Self::Serde => "serde",
|
||||
Self::Stdio => "stdio",
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +32,7 @@ where
|
|||
let res = match self {
|
||||
Self::Luau => luau::create(lua),
|
||||
Self::Task => task::create(lua),
|
||||
Self::Serde => serde::create(lua),
|
||||
Self::Stdio => stdio::create(lua),
|
||||
};
|
||||
match res {
|
||||
|
@ -47,6 +51,7 @@ impl FromStr for LuneBuiltin {
|
|||
match s.trim().to_ascii_lowercase().as_str() {
|
||||
"luau" => Ok(Self::Luau),
|
||||
"task" => Ok(Self::Task),
|
||||
"serde" => Ok(Self::Serde),
|
||||
"stdio" => Ok(Self::Stdio),
|
||||
_ => Err(format!("Unknown builtin library '{s}'")),
|
||||
}
|
||||
|
|
162
src/lune/builtins/serde/compress_decompress.rs
Normal file
162
src/lune/builtins/serde/compress_decompress.rs
Normal file
|
@ -0,0 +1,162 @@
|
|||
use lz4_flex::{compress_prepend_size, decompress_size_prepended};
|
||||
use mlua::prelude::*;
|
||||
use tokio::{
|
||||
io::{copy, BufReader},
|
||||
task,
|
||||
};
|
||||
|
||||
use async_compression::{
|
||||
tokio::bufread::{
|
||||
BrotliDecoder, BrotliEncoder, GzipDecoder, GzipEncoder, ZlibDecoder, ZlibEncoder,
|
||||
},
|
||||
Level::Best as CompressionQuality,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CompressDecompressFormat {
|
||||
Brotli,
|
||||
GZip,
|
||||
LZ4,
|
||||
ZLib,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl CompressDecompressFormat {
|
||||
pub fn detect_from_bytes(bytes: impl AsRef<[u8]>) -> Option<Self> {
|
||||
match bytes.as_ref() {
|
||||
// https://github.com/PSeitz/lz4_flex/blob/main/src/frame/header.rs#L28
|
||||
b if b.len() >= 4
|
||||
&& matches!(
|
||||
u32::from_le_bytes(b[0..4].try_into().unwrap()),
|
||||
0x184D2204 | 0x184C2102
|
||||
) =>
|
||||
{
|
||||
Some(Self::LZ4)
|
||||
}
|
||||
// https://github.com/dropbox/rust-brotli/blob/master/src/enc/brotli_bit_stream.rs#L2805
|
||||
b if b.len() >= 4
|
||||
&& matches!(
|
||||
b[0..3],
|
||||
[0xE1, 0x97, 0x81] | [0xE1, 0x97, 0x82] | [0xE1, 0x97, 0x80]
|
||||
) =>
|
||||
{
|
||||
Some(Self::Brotli)
|
||||
}
|
||||
// https://github.com/rust-lang/flate2-rs/blob/main/src/gz/mod.rs#L135
|
||||
b if b.len() >= 3 && matches!(b[0..3], [0x1F, 0x8B, 0x08]) => Some(Self::GZip),
|
||||
// https://stackoverflow.com/a/43170354
|
||||
b if b.len() >= 2
|
||||
&& matches!(
|
||||
b[0..2],
|
||||
[0x78, 0x01] | [0x78, 0x5E] | [0x78, 0x9C] | [0x78, 0xDA]
|
||||
) =>
|
||||
{
|
||||
Some(Self::ZLib)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_from_header_str(header: impl AsRef<str>) -> Option<Self> {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding#directives
|
||||
match header.as_ref().to_ascii_lowercase().trim() {
|
||||
"br" | "brotli" => Some(Self::Brotli),
|
||||
"deflate" => Some(Self::ZLib),
|
||||
"gz" | "gzip" => Some(Self::GZip),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for CompressDecompressFormat {
|
||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
if let LuaValue::String(s) = &value {
|
||||
match s.to_string_lossy().to_ascii_lowercase().trim() {
|
||||
"brotli" => Ok(Self::Brotli),
|
||||
"gzip" => Ok(Self::GZip),
|
||||
"lz4" => Ok(Self::LZ4),
|
||||
"zlib" => Ok(Self::ZLib),
|
||||
kind => Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "CompressDecompressFormat",
|
||||
message: Some(format!(
|
||||
"Invalid format '{kind}', valid formats are: brotli, gzip, lz4, zlib"
|
||||
)),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "CompressDecompressFormat",
|
||||
message: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn compress<'lua>(
|
||||
format: CompressDecompressFormat,
|
||||
source: impl AsRef<[u8]>,
|
||||
) -> LuaResult<Vec<u8>> {
|
||||
if let CompressDecompressFormat::LZ4 = format {
|
||||
let source = source.as_ref().to_vec();
|
||||
return task::spawn_blocking(move || compress_prepend_size(&source))
|
||||
.await
|
||||
.into_lua_err();
|
||||
}
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
let reader = BufReader::new(source.as_ref());
|
||||
|
||||
match format {
|
||||
CompressDecompressFormat::Brotli => {
|
||||
let mut encoder = BrotliEncoder::with_quality(reader, CompressionQuality);
|
||||
copy(&mut encoder, &mut bytes).await?;
|
||||
}
|
||||
CompressDecompressFormat::GZip => {
|
||||
let mut encoder = GzipEncoder::with_quality(reader, CompressionQuality);
|
||||
copy(&mut encoder, &mut bytes).await?;
|
||||
}
|
||||
CompressDecompressFormat::ZLib => {
|
||||
let mut encoder = ZlibEncoder::with_quality(reader, CompressionQuality);
|
||||
copy(&mut encoder, &mut bytes).await?;
|
||||
}
|
||||
CompressDecompressFormat::LZ4 => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
pub async fn decompress<'lua>(
|
||||
format: CompressDecompressFormat,
|
||||
source: impl AsRef<[u8]>,
|
||||
) -> LuaResult<Vec<u8>> {
|
||||
if let CompressDecompressFormat::LZ4 = format {
|
||||
let source = source.as_ref().to_vec();
|
||||
return task::spawn_blocking(move || decompress_size_prepended(&source))
|
||||
.await
|
||||
.into_lua_err()?
|
||||
.into_lua_err();
|
||||
}
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
let reader = BufReader::new(source.as_ref());
|
||||
|
||||
match format {
|
||||
CompressDecompressFormat::Brotli => {
|
||||
let mut decoder = BrotliDecoder::new(reader);
|
||||
copy(&mut decoder, &mut bytes).await?;
|
||||
}
|
||||
CompressDecompressFormat::GZip => {
|
||||
let mut decoder = GzipDecoder::new(reader);
|
||||
copy(&mut decoder, &mut bytes).await?;
|
||||
}
|
||||
CompressDecompressFormat::ZLib => {
|
||||
let mut decoder = ZlibDecoder::new(reader);
|
||||
copy(&mut decoder, &mut bytes).await?;
|
||||
}
|
||||
CompressDecompressFormat::LZ4 => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
133
src/lune/builtins/serde/encode_decode.rs
Normal file
133
src/lune/builtins/serde/encode_decode.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_yaml::Value 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()
|
||||
.deny_recursive_tables(false)
|
||||
.deny_unsupported_types(true);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum EncodeDecodeFormat {
|
||||
Json,
|
||||
Yaml,
|
||||
Toml,
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for EncodeDecodeFormat {
|
||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
if let LuaValue::String(s) = &value {
|
||||
match s.to_string_lossy().to_ascii_lowercase().trim() {
|
||||
"json" => Ok(Self::Json),
|
||||
"yaml" => Ok(Self::Yaml),
|
||||
"toml" => Ok(Self::Toml),
|
||||
kind => Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "EncodeDecodeFormat",
|
||||
message: Some(format!(
|
||||
"Invalid format '{kind}', valid formats are: json, yaml, toml"
|
||||
)),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "EncodeDecodeFormat",
|
||||
message: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EncodeDecodeConfig {
|
||||
pub format: EncodeDecodeFormat,
|
||||
pub pretty: bool,
|
||||
}
|
||||
|
||||
impl EncodeDecodeConfig {
|
||||
pub fn serialize_to_string<'lua>(
|
||||
self,
|
||||
lua: &'lua Lua,
|
||||
value: LuaValue<'lua>,
|
||||
) -> LuaResult<LuaString<'lua>> {
|
||||
let bytes = match self.format {
|
||||
EncodeDecodeFormat::Json => {
|
||||
let serialized: JsonValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
|
||||
if self.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)?;
|
||||
let mut writer = Vec::with_capacity(128);
|
||||
serde_yaml::to_writer(&mut writer, &serialized).into_lua_err()?;
|
||||
writer
|
||||
}
|
||||
EncodeDecodeFormat::Toml => {
|
||||
let serialized: TomlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
|
||||
let s = if self.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 deserialize_from_string<'lua>(
|
||||
self,
|
||||
lua: &'lua Lua,
|
||||
string: LuaString<'lua>,
|
||||
) -> LuaResult<LuaValue<'lua>> {
|
||||
let bytes = string.as_bytes();
|
||||
match self.format {
|
||||
EncodeDecodeFormat::Json => {
|
||||
let value: JsonValue = serde_json::from_slice(bytes).into_lua_err()?;
|
||||
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
|
||||
}
|
||||
EncodeDecodeFormat::Yaml => {
|
||||
let value: YamlValue = serde_yaml::from_slice(bytes).into_lua_err()?;
|
||||
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
|
||||
}
|
||||
EncodeDecodeFormat::Toml => {
|
||||
if let Ok(s) = string.to_str() {
|
||||
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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
50
src/lune/builtins/serde/mod.rs
Normal file
50
src/lune/builtins/serde/mod.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
mod compress_decompress;
|
||||
use compress_decompress::{compress, decompress, CompressDecompressFormat};
|
||||
|
||||
mod encode_decode;
|
||||
use encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat};
|
||||
|
||||
use crate::lune::util::TableBuilder;
|
||||
|
||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("encode", serde_encode)?
|
||||
.with_function("decode", serde_decode)?
|
||||
.with_async_function("compress", serde_compress)?
|
||||
.with_async_function("decompress", serde_decompress)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn serde_encode<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(format, val, pretty): (EncodeDecodeFormat, LuaValue<'lua>, Option<bool>),
|
||||
) -> LuaResult<LuaString<'lua>> {
|
||||
let config = EncodeDecodeConfig::from((format, pretty.unwrap_or_default()));
|
||||
config.serialize_to_string(lua, val)
|
||||
}
|
||||
|
||||
fn serde_decode<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(format, str): (EncodeDecodeFormat, LuaString<'lua>),
|
||||
) -> LuaResult<LuaValue<'lua>> {
|
||||
let config = EncodeDecodeConfig::from(format);
|
||||
config.deserialize_from_string(lua, str)
|
||||
}
|
||||
|
||||
async fn serde_compress<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(format, str): (CompressDecompressFormat, LuaString<'lua>),
|
||||
) -> LuaResult<LuaString<'lua>> {
|
||||
let bytes = compress(format, str).await?;
|
||||
lua.create_string(bytes)
|
||||
}
|
||||
|
||||
async fn serde_decompress<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(format, str): (CompressDecompressFormat, LuaString<'lua>),
|
||||
) -> LuaResult<LuaString<'lua>> {
|
||||
let bytes = decompress(format, str).await?;
|
||||
lua.create_string(bytes)
|
||||
}
|
Loading…
Reference in a new issue