Add SerializeOptions to to change default Lua serializer behaviour

This commit is contained in:
Alex Orlenko 2021-04-27 00:21:49 +01:00
parent c19f12898d
commit bc81d1016f
5 changed files with 229 additions and 28 deletions

View File

@ -118,7 +118,7 @@ pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti
pub use crate::thread::AsyncThread;
#[cfg(feature = "serialize")]
pub use crate::serde::LuaSerdeExt;
pub use crate::serde::{ser::Options as SerializeOptions, LuaSerdeExt};
pub mod prelude;
#[cfg(feature = "serialize")]

View File

@ -14,3 +14,6 @@ pub use crate::{
#[cfg(feature = "async")]
pub use crate::AsyncThread as LuaAsyncThread;
#[cfg(feature = "serialize")]
pub use crate::{LuaSerdeExt, SerializeOptions as LuaSerializeOptions};

View File

@ -101,6 +101,35 @@ pub trait LuaSerdeExt<'lua> {
/// ```
fn to_value<T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
/// Converts `T` into a `Value` instance with options.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt, SerializeOptions};
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let v = vec![1, 2, 3];
/// lua.globals().set("v", lua.to_value_with(&v, SerializeOptions {
/// set_array_metatable: false,
/// ..SerializeOptions::default()
/// })?)?;
///
/// lua.load(r#"
/// assert(#v == 3 and v[1] == 1 and v[2] == 2 and v[3] == 3)
/// assert(getmetatable(v) == nil)
/// "#).exec()
/// }
/// ```
fn to_value_with<T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
where
T: Serialize + ?Sized;
/// Deserializes a `Value` into any serde deserializable object.
///
/// Requires `feature = "serialize"`
@ -153,7 +182,14 @@ impl<'lua> LuaSerdeExt<'lua> for Lua {
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer(self))
t.serialize(ser::Serializer::new(self))
}
fn to_value_with<T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer::new_with_options(self, options))
}
fn from_value<T>(&'lua self, value: Value<'lua>) -> Result<T>

View File

@ -13,13 +13,68 @@ use crate::util::{assert_stack, StackGuard};
use crate::value::{ToLua, Value};
/// A struct for serializing Rust values into Lua values.
pub struct Serializer<'lua>(pub &'lua Lua);
pub struct Serializer<'lua> {
lua: &'lua Lua,
options: Options,
}
/// A struct with options to change default serializer behaviour.
#[derive(Debug, Clone, Copy)]
pub struct Options {
/// If true, sequence serialization to a Lua table will create table
/// with the [`array_metatable`] attached.
///
/// Default: true
///
/// [`array_metatable`]: trait.LuaSerdeExt.html#tymethod.array_metatable
pub set_array_metatable: bool,
/// If true, serialize `None` (part of `Option` type) to [`null`].
/// Otherwise it will be set to Lua [`Nil`].
///
/// Default: true
///
/// [`null`]: trait.LuaSerdeExt.html#tymethod.null
/// [`Nil`]: ../enum.Value.html#variant.Nil
pub serialize_none_to_null: bool,
/// If true, serialize `Unit` (type of `()` in Rust) and Unit structs to [`null`].
/// Otherwise it will be set to Lua [`Nil`].
///
/// Default: true
///
/// [`null`]: trait.LuaSerdeExt.html#tymethod.null
/// [`Nil`]: ../enum.Value.html#variant.Nil
pub serialize_unit_to_null: bool,
}
impl Default for Options {
fn default() -> Self {
Options {
set_array_metatable: true,
serialize_none_to_null: true,
serialize_unit_to_null: true,
}
}
}
impl<'lua> Serializer<'lua> {
/// Creates a new instance of Lua Serializer with default options
pub fn new(lua: &'lua Lua) -> Self {
Self::new_with_options(lua, Options::default())
}
/// Creates a new instance of Lua Serializer with custom options
pub fn new_with_options(lua: &'lua Lua, options: Options) -> Self {
Serializer { lua, options }
}
}
macro_rules! lua_serialize_number {
($name:ident, $t:ty) => {
#[inline]
fn $name(self, value: $t) -> Result<Value<'lua>> {
value.to_lua(self.0)
value.to_lua(self.lua)
}
};
}
@ -62,17 +117,21 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline]
fn serialize_str(self, value: &str) -> Result<Value<'lua>> {
self.0.create_string(value).map(Value::String)
self.lua.create_string(value).map(Value::String)
}
#[inline]
fn serialize_bytes(self, value: &[u8]) -> Result<Value<'lua>> {
self.0.create_string(value).map(Value::String)
self.lua.create_string(value).map(Value::String)
}
#[inline]
fn serialize_none(self) -> Result<Value<'lua>> {
self.0.null()
if self.options.serialize_none_to_null {
self.lua.null()
} else {
Ok(Value::Nil)
}
}
#[inline]
@ -85,12 +144,20 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline]
fn serialize_unit(self) -> Result<Value<'lua>> {
self.0.null()
if self.options.serialize_unit_to_null {
self.lua.null()
} else {
Ok(Value::Nil)
}
}
#[inline]
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value<'lua>> {
self.0.null()
if self.options.serialize_unit_to_null {
self.lua.null()
} else {
Ok(Value::Nil)
}
}
#[inline]
@ -122,9 +189,9 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
where
T: ?Sized + Serialize,
{
let table = self.0.create_table()?;
let variant = self.0.create_string(variant)?;
let value = self.0.to_value(value)?;
let table = self.lua.create_table()?;
let variant = self.lua.create_string(variant)?;
let value = self.lua.to_value_with(value, self.options)?;
table.raw_set(variant, value)?;
Ok(Value::Table(table))
}
@ -132,9 +199,12 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline]
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let len = len.unwrap_or(0) as c_int;
let table = self.0.create_table_with_capacity(len, 0)?;
table.set_metatable(Some(self.0.array_metatable()?));
Ok(SerializeVec { table })
let table = self.lua.create_table_with_capacity(len, 0)?;
if self.options.set_array_metatable {
table.set_metatable(Some(self.lua.array_metatable()?));
}
let options = self.options;
Ok(SerializeVec { table, options })
}
#[inline]
@ -159,9 +229,11 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
let name = self.0.create_string(variant)?;
let table = self.0.create_table()?;
Ok(SerializeTupleVariant { name, table })
Ok(SerializeTupleVariant {
name: self.lua.create_string(variant)?,
table: self.lua.create_table()?,
options: self.options,
})
}
#[inline]
@ -169,7 +241,8 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
let len = len.unwrap_or(0) as c_int;
Ok(SerializeMap {
key: None,
table: self.0.create_table_with_capacity(0, len)?,
table: self.lua.create_table_with_capacity(0, len)?,
options: self.options,
})
}
@ -186,14 +259,17 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant> {
let name = self.0.create_string(variant)?;
let table = self.0.create_table_with_capacity(0, len as c_int)?;
Ok(SerializeStructVariant { name, table })
Ok(SerializeStructVariant {
name: self.lua.create_string(variant)?,
table: self.lua.create_table_with_capacity(0, len as c_int)?,
options: self.options,
})
}
}
pub struct SerializeVec<'lua> {
table: Table<'lua>,
options: Options,
}
impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
@ -205,7 +281,7 @@ impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
let value = lua.to_value(value)?;
let value = lua.to_value_with(value, self.options)?;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 5);
@ -257,6 +333,7 @@ impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> {
pub struct SerializeTupleVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
options: Options,
}
impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
@ -269,7 +346,8 @@ impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
{
let lua = self.table.0.lua;
let idx = self.table.raw_len() + 1;
self.table.raw_insert(idx, lua.to_value(value)?)
self.table
.raw_insert(idx, lua.to_value_with(value, self.options)?)
}
fn end(self) -> Result<Value<'lua>> {
@ -283,6 +361,7 @@ impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
pub struct SerializeMap<'lua> {
table: Table<'lua>,
key: Option<Value<'lua>>,
options: Options,
}
impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
@ -294,7 +373,7 @@ impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
self.key = Some(lua.to_value(key)?);
self.key = Some(lua.to_value_with(key, self.options)?);
Ok(())
}
@ -307,7 +386,7 @@ impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
self.key.take(),
"serialize_value called before serialize_key"
);
let value = lua.to_value(value)?;
let value = lua.to_value_with(value, self.options)?;
self.table.raw_set(key, value)
}
@ -336,6 +415,7 @@ impl<'lua> ser::SerializeStruct for SerializeMap<'lua> {
pub struct SerializeStructVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
options: Options,
}
impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> {
@ -347,7 +427,8 @@ impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> {
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
self.table.raw_set(key, lua.to_value(value)?)?;
self.table
.raw_set(key, lua.to_value_with(value, self.options)?)?;
Ok(())
}

View File

@ -1,6 +1,8 @@
#![cfg(feature = "serialize")]
use mlua::{Error, Lua, LuaSerdeExt, Result as LuaResult, UserData, Value};
use std::collections::HashMap;
use mlua::{Error, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, UserData, Value};
use serde::{Deserialize, Serialize};
#[test]
@ -230,6 +232,85 @@ fn test_to_value_enum() -> LuaResult<()> {
Ok(())
}
#[test]
fn test_to_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null()?)?;
// set_array_metatable
let data = lua.to_value_with(
&Vec::<i32>::new(),
SerializeOptions {
set_array_metatable: false,
..SerializeOptions::default()
},
)?;
globals.set("data", data)?;
lua.load(
r#"
assert(type(data) == "table" and #data == 0)
assert(getmetatable(data) == nil)
"#,
)
.exec()?;
#[derive(Serialize)]
struct UnitStruct;
#[derive(Serialize)]
struct MyData {
map: HashMap<&'static str, Option<i32>>,
unit: (),
unitstruct: UnitStruct,
}
// serialize_none_to_null
let mut map = HashMap::new();
map.insert("key", None);
let mydata = MyData {
map,
unit: (),
unitstruct: UnitStruct,
};
let data2 = lua.to_value_with(
&mydata,
SerializeOptions {
serialize_none_to_null: false,
..SerializeOptions::default()
},
)?;
globals.set("data2", data2)?;
lua.load(
r#"
assert(data2.map.key == nil)
assert(data2.unit == null)
assert(data2.unitstruct == null)
"#,
)
.exec()?;
// serialize_unit_to_null
let data3 = lua.to_value_with(
&mydata,
SerializeOptions {
serialize_unit_to_null: false,
..SerializeOptions::default()
},
)?;
globals.set("data3", data3)?;
lua.load(
r#"
assert(data3.map.key == null)
assert(data3.unit == nil)
assert(data3.unitstruct == nil)
"#,
)
.exec()?;
Ok(())
}
#[test]
fn test_from_value_struct() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();