#![cfg(feature = "serde")] use std::collections::HashMap; use std::error::Error as StdError; use bstr::BString; use mlua::{ AnyUserData, DeserializeOptions, Error, ExternalResult, IntoLua, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, UserData, Value, }; use serde::{Deserialize, Serialize}; #[test] fn test_serialize() -> Result<(), Box> { #[derive(Serialize)] struct MyUserData(i64, String); impl UserData for MyUserData {} let lua = Lua::new(); let globals = lua.globals(); let ud = lua.create_ser_userdata(MyUserData(123, "test userdata".into()))?; globals.set("ud", ud)?; globals.set("null", lua.null())?; let empty_array = lua.create_table()?; empty_array.set_metatable(Some(lua.array_metatable()))?; globals.set("empty_array", empty_array)?; let val = lua .load( r#" { _bool = true, _integer = 123, _number = 321.99, _string = "test string serialization", _table_arr = {null, "value 1", 2, "value 3", {}}, _table_map = {["table"] = "map", ["null"] = null}, _bytes = "\240\040\140\040", _userdata = ud, _null = null, _empty_map = {}, _empty_array = empty_array, } "#, ) .eval::()?; let json = serde_json::json!({ "_bool": true, "_integer": 123, "_number": 321.99, "_string": "test string serialization", "_table_arr": [null, "value 1", 2, "value 3", {}], "_table_map": {"table": "map", "null": null}, "_bytes": [240, 40, 140, 40], "_userdata": [123, "test userdata"], "_null": null, "_empty_map": {}, "_empty_array": [], }); assert_eq!(serde_json::to_value(&val)?, json); // Test to-from loop let val = lua.to_value(&json)?; let expected_json = lua.from_value::(val)?; assert_eq!(expected_json, json); Ok(()) } #[test] fn test_serialize_any_userdata() { let lua = Lua::new(); let json_val = serde_json::json!({ "a": 1, "b": "test", }); let json_ud = lua.create_ser_any_userdata(json_val).unwrap(); let json_str = serde_json::to_string_pretty(&json_ud).unwrap(); assert_eq!(json_str, "{\n \"a\": 1,\n \"b\": \"test\"\n}"); } #[test] fn test_serialize_wrapped_any_userdata() { let lua = Lua::new(); let json_val = serde_json::json!({ "a": 1, "b": "test", }); let ud = AnyUserData::wrap_ser(json_val); let json_ud = ud.into_lua(&lua).unwrap(); let json_str = serde_json::to_string(&json_ud).unwrap(); assert_eq!(json_str, "{\"a\":1,\"b\":\"test\"}"); } #[test] fn test_serialize_failure() -> Result<(), Box> { #[derive(Serialize)] struct MyUserData(i64); impl UserData for MyUserData {} let lua = Lua::new(); let ud = Value::UserData(lua.create_userdata(MyUserData(123))?); match serde_json::to_value(&ud) { Ok(v) => panic!("expected serialization error, got {}", v), Err(serde_json::Error { .. }) => {} } let func = lua.create_function(|_, _: ()| Ok(()))?; match serde_json::to_value(&Value::Function(func.clone())) { Ok(v) => panic!("expected serialization error, got {}", v), Err(serde_json::Error { .. }) => {} } let thr = lua.create_thread(func)?; match serde_json::to_value(&Value::Thread(thr)) { Ok(v) => panic!("expected serialization error, got {}", v), Err(serde_json::Error { .. }) => {} } Ok(()) } #[cfg(all(feature = "luau", not(feature = "luau-vector4")))] #[test] fn test_serialize_vector() -> Result<(), Box> { let lua = Lua::new(); let val = lua.load("{_vector = vector.create(1, 2, 3)}").eval::()?; let json = serde_json::json!({ "_vector": [1.0, 2.0, 3.0], }); assert_eq!(serde_json::to_value(&val)?, json); let expected_json = lua.from_value::(val)?; assert_eq!(expected_json, json); Ok(()) } #[cfg(feature = "luau-vector4")] #[test] fn test_serialize_vector() -> Result<(), Box> { let lua = Lua::new(); let val = lua .load("{_vector = vector.create(1, 2, 3, 4)}") .eval::()?; let json = serde_json::json!({ "_vector": [1.0, 2.0, 3.0, 4.0], }); assert_eq!(serde_json::to_value(&val)?, json); let expected_json = lua.from_value::(val)?; assert_eq!(expected_json, json); Ok(()) } #[test] fn test_serialize_sorted() -> LuaResult<()> { let lua = Lua::new(); let globals = lua.globals(); globals.set("null", lua.null())?; let empty_array = lua.create_table()?; empty_array.set_metatable(Some(lua.array_metatable()))?; globals.set("empty_array", empty_array)?; let value = lua .load( r#" { _bool = true, _integer = 123, _number = 321.99, _string = "test string serialization", _table_arr = {null, "value 1", 2, "value 3", {}}, _table_map = {["table"] = "map", ["null"] = null}, _bytes = "\240\040\140\040", _null = null, _empty_map = {}, _empty_array = empty_array, } "#, ) .eval::()?; let json = serde_json::to_string(&value.to_serializable().sort_keys(true)).unwrap(); assert_eq!( json, r#"{"_bool":true,"_bytes":[240,40,140,40],"_empty_array":[],"_empty_map":{},"_integer":123,"_null":null,"_number":321.99,"_string":"test string serialization","_table_arr":[null,"value 1",2,"value 3",{}],"_table_map":{"null":null,"table":"map"}}"# ); Ok(()) } #[test] fn test_serialize_globals() -> LuaResult<()> { let lua = Lua::new(); let globals = Value::Table(lua.globals()); // By default it should not work if let Ok(v) = serde_json::to_value(&globals) { panic!("expected serialization error, got {v:?}"); } // It should work with `deny_recursive_tables` and `deny_unsupported_types` disabled if let Err(err) = serde_json::to_value( globals .to_serializable() .deny_recursive_tables(false) .deny_unsupported_types(false), ) { panic!("expected no errors, got {err:?}"); } Ok(()) } #[test] fn test_serialize_same_table_twice() -> LuaResult<()> { let lua = Lua::new(); let value = lua .load( r#" local foo = {} return { a = foo, b = foo, } "#, ) .eval::()?; let json = serde_json::to_string(&value.to_serializable().sort_keys(true)).unwrap(); assert_eq!(json, r#"{"a":{},"b":{}}"#); Ok(()) } #[test] fn test_serialize_empty_table() -> LuaResult<()> { let lua = Lua::new(); let table = Value::Table(lua.create_table()?); let json = serde_json::to_string(&table.to_serializable()).unwrap(); assert_eq!(json, "{}"); // Set the option to encode empty tables as array let json = serde_json::to_string(&table.to_serializable().encode_empty_tables_as_array(true)).unwrap(); assert_eq!(json, "[]"); // Check hashmap table with this option table.as_table().unwrap().set("hello", "world")?; let json = serde_json::to_string(&table.to_serializable().encode_empty_tables_as_array(true)).unwrap(); assert_eq!(json, r#"{"hello":"world"}"#); Ok(()) } #[test] fn test_serialize_mixed_table() -> LuaResult<()> { let lua = Lua::new(); // Check that sparse array is serialized similarly when using direct serialization // and via `Lua::from_value` let table = lua.load("{1,2,3,nil,5}").eval::()?; let json1 = serde_json::to_string(&table).unwrap(); let json2 = lua.from_value::(table)?; assert_eq!(json1, json2.to_string()); // A table with several borders should be correctly encoded when `detect_mixed_tables` is enabled let table = lua .load( r#" local t = {1,2,3,nil,5,6} t[10] = 10 return t "#, ) .eval::()?; let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap(); assert_eq!(json, r#"[1,2,3,null,5,6,null,null,null,10]"#); // A mixed table with both array-like and map-like entries let table = lua.load(r#"{1,2,3, key="value"}"#).eval::()?; let json = serde_json::to_string(&table).unwrap(); assert_eq!(json, r#"[1,2,3]"#); let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap(); assert_eq!(json, r#"{"1":1,"2":2,"3":3,"key":"value"}"#); // A mixed table with duplicate keys of different types let table = lua.load(r#"{1,2,3, ["1"]="value"}"#).eval::()?; let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap(); assert_eq!(json, r#"{"1":1,"2":2,"3":3,"1":"value"}"#); Ok(()) } #[test] fn test_to_value_struct() -> LuaResult<()> { let lua = Lua::new(); let globals = lua.globals(); globals.set("null", lua.null())?; #[derive(Serialize)] struct Test { name: String, key: i64, data: Option, } let test = Test { name: "alex".to_string(), key: -16, data: None, }; globals.set("value", lua.to_value(&test)?)?; lua.load( r#" assert(value["name"] == "alex") assert(value["key"] == -16) assert(value["data"] == null) "#, ) .exec() } #[test] fn test_to_value_enum() -> LuaResult<()> { let lua = Lua::new(); let globals = lua.globals(); #[derive(Serialize)] enum E { Unit, Integer(u32), Tuple(u32, u32), Struct { a: u32 }, } let u = E::Unit; globals.set("value", lua.to_value(&u)?)?; lua.load(r#"assert(value == "Unit")"#).exec()?; let n = E::Integer(1); globals.set("value", lua.to_value(&n)?)?; lua.load(r#"assert(value["Integer"] == 1)"#).exec()?; let t = E::Tuple(1, 2); globals.set("value", lua.to_value(&t)?)?; lua.load( r#" assert(value["Tuple"][1] == 1) assert(value["Tuple"][2] == 2) "#, ) .exec()?; let s = E::Struct { a: 1 }; globals.set("value", lua.to_value(&s)?)?; lua.load(r#"assert(value["Struct"]["a"] == 1)"#).exec()?; Ok(()) } #[test] fn test_to_value_with_options() -> Result<(), Box> { let lua = Lua::new(); let globals = lua.globals(); globals.set("null", lua.null())?; // set_array_metatable let data = lua.to_value_with( &Vec::::new(), SerializeOptions::new().set_array_metatable(false), )?; 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>, 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::new().serialize_none_to_null(false))?; 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::new().serialize_unit_to_null(false))?; 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_nested_tables() -> Result<(), Box> { let lua = Lua::new(); let value = lua .load( r#" local table_a = {a = "a"} local table_b = {"b"} return { a = table_a, b = {table_b, table_b}, ab = {a = table_a, b = table_b} } "#, ) .eval::()?; let got = lua.from_value::(value)?; assert_eq!( got, serde_json::json!({ "a": {"a": "a"}, "b": [["b"], ["b"]], "ab": {"a": {"a": "a"}, "b": ["b"]}, }) ); Ok(()) } #[test] fn test_from_value_struct() -> Result<(), Box> { let lua = Lua::new(); #[derive(Deserialize, PartialEq, Debug)] struct Test { int: u32, seq: Vec, map: HashMap, empty: Vec<()>, tuple: (u8, u8, u8), bytes: BString, } let value = lua .load( r#" { int = 1, seq = {"a", "b"}, map = {2, [4] = 1}, empty = {}, tuple = {10, 20, 30}, bytes = "\240\040\140\040", } "#, ) .eval::()?; let got = lua.from_value(value)?; assert_eq!( Test { int: 1, seq: vec!["a".into(), "b".into()], map: vec![(1, 2), (4, 1)].into_iter().collect(), empty: vec![], tuple: (10, 20, 30), bytes: BString::from([240, 40, 140, 40]), }, got ); Ok(()) } #[test] fn test_from_value_newtype_struct() -> Result<(), Box> { let lua = Lua::new(); #[derive(Deserialize, PartialEq, Debug)] struct Test(f64); let got = lua.from_value(Value::Number(123.456))?; assert_eq!(Test(123.456), got); Ok(()) } #[test] fn test_from_value_enum() -> Result<(), Box> { let lua = Lua::new(); lua.globals().set("null", lua.null())?; #[derive(Deserialize, PartialEq, Debug)] struct UnitStruct; #[derive(Deserialize, PartialEq, Debug)] enum E { Unit, Integer(u32), Tuple(u32, u32), Struct { a: u32 }, Wrap(T), } let value = lua.load(r#""Unit""#).eval()?; let got: E = lua.from_value(value)?; assert_eq!(E::Unit, got); let value = lua.load(r#"{Integer = 1}"#).eval()?; let got: E = lua.from_value(value)?; assert_eq!(E::Integer(1), got); let value = lua.load(r#"{Tuple = {1, 2}}"#).eval()?; let got: E = lua.from_value(value)?; assert_eq!(E::Tuple(1, 2), got); let value = lua.load(r#"{Struct = {a = 3}}"#).eval()?; let got: E = lua.from_value(value)?; assert_eq!(E::Struct { a: 3 }, got); let value = lua.load(r#"{Wrap = null}"#).eval()?; let got = lua.from_value(value)?; assert_eq!(E::Wrap(UnitStruct), got); let value = lua.load(r#"{Wrap = null}"#).eval()?; let got = lua.from_value(value)?; assert_eq!(E::Wrap(()), got); Ok(()) } #[test] fn test_from_value_enum_untagged() -> Result<(), Box> { let lua = Lua::new(); lua.globals().set("null", lua.null())?; #[derive(Deserialize, PartialEq, Debug)] #[serde(untagged)] enum Eut { Unit, Integer(u64), Tuple(u32, u32), Struct { a: u32 }, } let value = lua.load(r#"null"#).eval()?; let got = lua.from_value(value)?; assert_eq!(Eut::Unit, got); let value = lua.load(r#"1"#).eval()?; let got = lua.from_value(value)?; assert_eq!(Eut::Integer(1), got); let value = lua.load(r#"{3, 1}"#).eval()?; let got = lua.from_value(value)?; assert_eq!(Eut::Tuple(3, 1), got); let value = lua.load(r#"{a = 10}"#).eval()?; let got = lua.from_value(value)?; assert_eq!(Eut::Struct { a: 10 }, got); let value = lua.load(r#"{b = 12}"#).eval()?; match lua.from_value::(value) { Ok(v) => panic!("expected Error::DeserializeError, got {:?}", v), Err(Error::DeserializeError(_)) => {} Err(e) => panic!("expected Error::DeserializeError, got {}", e), } Ok(()) } #[test] fn test_from_value_with_options() -> Result<(), Box> { let lua = Lua::new(); // Deny unsupported types by default let value = Value::Function(lua.create_function(|_, ()| Ok(()))?); match lua.from_value::>(value) { Ok(v) => panic!("expected deserialization error, got {:?}", v), Err(Error::DeserializeError(err)) => { assert!(err.contains("unsupported value type")) } Err(err) => panic!("expected `DeserializeError` error, got {:?}", err), }; // Allow unsupported types let value = Value::Function(lua.create_function(|_, ()| Ok(()))?); let options = DeserializeOptions::new().deny_unsupported_types(false); assert_eq!(lua.from_value_with::<()>(value, options)?, ()); // Allow unsupported types (in a table seq) let value = lua.load(r#"{"a", "b", function() end, "c"}"#).eval()?; let options = DeserializeOptions::new().deny_unsupported_types(false); assert_eq!( lua.from_value_with::>(value, options)?, vec!["a".to_string(), "b".to_string(), "c".to_string()] ); // Deny recursive tables by default let value = lua.load(r#"local t = {}; t.t = t; return t"#).eval()?; match lua.from_value::>>(value) { Ok(v) => panic!("expected deserialization error, got {:?}", v), Err(Error::DeserializeError(err)) => { assert!(err.contains("recursive table detected")) } Err(err) => panic!("expected `DeserializeError` error, got {:?}", err), }; // Check recursion when using `Serialize` impl let t = lua.create_table()?; t.set("t", &t)?; assert!(serde_json::to_string(&t).is_err()); // Serialize Lua globals table #[derive(Debug, Deserialize)] struct Globals { hello: String, } let options = DeserializeOptions::new() .deny_unsupported_types(false) .deny_recursive_tables(false); lua.load(r#"hello = "world""#).exec()?; let globals: Globals = lua.from_value_with(Value::Table(lua.globals()), options)?; assert_eq!(globals.hello, "world"); Ok(()) } #[test] fn test_from_value_userdata() -> Result<(), Box> { let lua = Lua::new(); // Tuple struct #[derive(Serialize, Deserialize)] struct MyUserData(i64, String); impl UserData for MyUserData {} let ud = lua.create_ser_userdata(MyUserData(123, "test userdata".into()))?; match lua.from_value::(Value::UserData(ud)) { Ok(_) => {} Err(err) => panic!("expected no errors, got {err:?}"), }; // Newtype struct #[derive(Serialize, Deserialize)] struct NewtypeUserdata(String); impl UserData for NewtypeUserdata {} let ud = lua.create_ser_userdata(NewtypeUserdata("newtype userdata".into()))?; match lua.from_value::(Value::UserData(ud)) { Ok(_) => {} Err(err) => panic!("expected no errors, got {err:?}"), }; // Option #[derive(Serialize, Deserialize)] struct UnitUserdata; impl UserData for UnitUserdata {} let ud = lua.create_ser_userdata(UnitUserdata)?; match lua.from_value::>(Value::UserData(ud)) { Ok(Some(_)) => {} Ok(_) => panic!("expected `Some`, got `None`"), Err(err) => panic!("expected no errors, got {err:?}"), }; // Destructed userdata with skip option let ud = lua.create_ser_userdata(NewtypeUserdata("newtype userdata".into()))?; let _ = ud.take::()?; match lua.from_value_with::<()>( Value::UserData(ud), DeserializeOptions::new().deny_unsupported_types(false), ) { Ok(_) => {} Err(err) => panic!("expected no errors, got {err:?}"), }; Ok(()) } #[test] fn test_from_value_empty_table() -> Result<(), Box> { let lua = Lua::new(); // By default we encode empty tables as objects let t = lua.create_table()?; let got = lua.from_value::(Value::Table(t.clone()))?; assert_eq!(got, serde_json::json!({})); // Set the option to encode empty tables as array let got = lua .from_value_with::( Value::Table(t.clone()), DeserializeOptions::new().encode_empty_tables_as_array(true), ) .unwrap(); assert_eq!(got, serde_json::json!([])); // Check hashmap table with this option t.raw_set("hello", "world")?; let got = lua .from_value_with::( Value::Table(t), DeserializeOptions::new().encode_empty_tables_as_array(true), ) .unwrap(); assert_eq!(got, serde_json::json!({"hello": "world"})); Ok(()) } #[test] fn test_from_value_sorted() -> Result<(), Box> { let lua = Lua::new(); let to_json = lua.create_function(|lua, value| { let json_value: serde_json::Value = lua.from_value_with(value, DeserializeOptions::new().sort_keys(true))?; serde_json::to_string(&json_value).into_lua_err() })?; lua.globals().set("to_json", to_json)?; lua.load( r#" local json = to_json({c = 3, b = 2, hello = "world", x = {1}, ["0a"] = {z = "z", d = "d"}}) assert(json == '{"0a":{"d":"d","z":"z"},"b":2,"c":3,"hello":"world","x":[1]}', "invalid json") "#, ) .exec() .unwrap(); Ok(()) } #[test] fn test_arbitrary_precision() { let lua = Lua::new(); let opts = SerializeOptions::new().detect_serde_json_arbitrary_precision(true); // Number let num = serde_json::Value::Number(serde_json::Number::from_f64(1.244e2).unwrap()); let num = lua.to_value_with(&num, opts).unwrap(); assert_eq!(num, Value::Number(1.244e2)); // Integer let num = serde_json::Value::Number(serde_json::Number::from_f64(123.0).unwrap()); let num = lua.to_value_with(&num, opts).unwrap(); assert_eq!(num, Value::Integer(123)); // Max u64 let num = serde_json::Value::Number(serde_json::Number::from(i64::MAX)); let num = lua.to_value_with(&num, opts).unwrap(); assert_eq!(num, Value::Number(i64::MAX as f64)); // Check that the option is disabled by default let num = serde_json::Value::Number(serde_json::Number::from_f64(1.244e2).unwrap()); let num = lua.to_value(&num).unwrap(); assert_eq!(num.type_name(), "table"); assert_eq!( format!("{:#?}", num), "{\n [\"$serde_json::private::Number\"] = \"124.4\",\n}" ); } #[cfg(feature = "luau")] #[test] fn test_buffer_serialize() -> LuaResult<()> { let lua = Lua::new(); let buf = lua.create_buffer(&[1, 2, 3, 4])?; let val = serde_value::to_value(&buf).unwrap(); assert_eq!(val, serde_value::Value::Bytes(vec![1, 2, 3, 4])); // Try empty buffer let buf = lua.create_buffer(&[])?; let val = serde_value::to_value(&buf).unwrap(); assert_eq!(val, serde_value::Value::Bytes(vec![])); Ok(()) } #[cfg(feature = "luau")] #[test] fn test_buffer_from_value() -> LuaResult<()> { let lua = Lua::new(); let buf = lua.create_buffer(&[1, 2, 3, 4])?; let val = lua.from_value::(Value::Buffer(buf)).unwrap(); assert_eq!(val, serde_value::Value::Bytes(vec![1, 2, 3, 4])); Ok(()) }