From 529361fcbcd7e1c5ae57bc609f14ec75da0cffd2 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 1 Oct 2024 15:21:19 +0100 Subject: [PATCH] Add new `Buffer` type for Luau. Previously it was represented as `AnyUserData` which is not always convenient. --- src/buffer.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++ src/conversion.rs | 45 ++++++++++++++++++++---- src/lib.rs | 3 +- src/serde/de.rs | 11 ++---- src/state.rs | 13 ++++--- src/state/raw.rs | 5 +-- src/table.rs | 2 +- src/types.rs | 2 -- src/types/vector.rs | 2 +- src/userdata.rs | 15 -------- src/value.rs | 51 +++++++++++++++++++++------ tests/buffer.rs | 56 +++++++++++++++++++++++++++++ tests/conversion.rs | 4 +-- tests/luau.rs | 26 -------------- tests/serde.rs | 18 +++++----- 15 files changed, 248 insertions(+), 91 deletions(-) create mode 100644 src/buffer.rs create mode 100644 tests/buffer.rs diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..42b2008 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,86 @@ +#[cfg(feature = "serialize")] +use serde::ser::{Serialize, Serializer}; + +use crate::types::ValueRef; + +/// A Luau buffer type. +/// +/// See the buffer [documentation] for more information. +/// +/// [documentation]: https://luau.org/library#buffer-library +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] +#[derive(Clone, Debug, PartialEq)] +pub struct Buffer(pub(crate) ValueRef); + +#[cfg_attr(not(feature = "luau"), allow(unused))] +impl Buffer { + /// Copies the buffer data into a new `Vec`. + pub fn to_vec(&self) -> Vec { + unsafe { self.as_slice().to_vec() } + } + + /// Returns the length of the buffer. + pub fn len(&self) -> usize { + unsafe { self.as_slice().len() } + } + + /// Returns `true` if the buffer is empty. + #[doc(hidden)] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Reads given number of bytes from the buffer at the given offset. + /// + /// Offset is 0-based. + #[track_caller] + pub fn read_bytes(&self, offset: usize) -> [u8; N] { + let data = unsafe { self.as_slice() }; + let mut bytes = [0u8; N]; + bytes.copy_from_slice(&data[offset..offset + N]); + bytes + } + + /// Writes given bytes to the buffer at the given offset. + /// + /// Offset is 0-based. + #[track_caller] + pub fn write_bytes(&self, offset: usize, bytes: &[u8]) { + let data = unsafe { + let (buf, size) = self.as_raw_parts(); + std::slice::from_raw_parts_mut(buf, size) + }; + data[offset..offset + bytes.len()].copy_from_slice(bytes); + } + + pub(crate) unsafe fn as_slice(&self) -> &[u8] { + let (buf, size) = self.as_raw_parts(); + std::slice::from_raw_parts(buf, size) + } + + #[cfg(feature = "luau")] + unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { + let lua = self.0.lua.lock(); + let mut size = 0usize; + let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); + mlua_assert!(!buf.is_null(), "invalid Luau buffer"); + (buf as *mut u8, size) + } + + #[cfg(not(feature = "luau"))] + unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { + unreachable!() + } +} + +#[cfg(feature = "serialize")] +impl Serialize for Buffer { + fn serialize(&self, serializer: S) -> std::result::Result { + serializer.serialize_bytes(unsafe { self.as_slice() }) + } +} + +#[cfg(feature = "luau")] +impl crate::types::LuaType for Buffer { + const TYPE_ID: std::os::raw::c_int = ffi::LUA_TBUFFER; +} diff --git a/src/conversion.rs b/src/conversion.rs index 319b787..8bfd44b 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -367,6 +367,43 @@ impl FromLua for crate::types::Vector { } } +#[cfg(feature = "luau")] +impl IntoLua for crate::Buffer { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::Buffer(self)) + } +} + +#[cfg(feature = "luau")] +impl IntoLua for &crate::Buffer { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::Buffer(self.clone())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.0); + Ok(()) + } +} + +#[cfg(feature = "luau")] +impl FromLua for crate::Buffer { + #[inline] + fn from_lua(value: Value, _: &Lua) -> Result { + match value { + Value::Buffer(buf) => Ok(buf), + _ => Err(Error::FromLuaConversionError { + from: value.type_name(), + to: "buffer".to_string(), + message: None, + }), + } + } +} + impl IntoLua for StdString { #[inline] fn into_lua(self, lua: &Lua) -> Result { @@ -515,13 +552,7 @@ impl FromLua for BString { match value { Value::String(s) => Ok((*s.as_bytes()).into()), #[cfg(feature = "luau")] - Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { - let lua = ud.0.lua.lock(); - let mut size = 0usize; - let buf = ffi::lua_tobuffer(lua.ref_thread(), ud.0.index, &mut size); - mlua_assert!(!buf.is_null(), "invalid Luau buffer"); - Ok(slice::from_raw_parts(buf as *const u8, size).into()) - }, + Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) }, _ => Ok((*lua .coerce_string(value)? .ok_or_else(|| Error::FromLuaConversionError { diff --git a/src/lib.rs b/src/lib.rs index 5f8a026..049f334 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ #[macro_use] mod macros; +mod buffer; mod chunk; mod conversion; mod error; @@ -130,7 +131,7 @@ pub use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -pub use crate::{chunk::Compiler, function::CoverageInfo, types::Vector}; +pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, types::Vector}; #[cfg(feature = "async")] pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; diff --git a/src/serde/de.rs b/src/serde/de.rs index c54241a..6d3d513 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -145,14 +145,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer { serde_userdata(ud, |value| value.deserialize_any(visitor)) } #[cfg(feature = "luau")] - Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe { - let lua = ud.0.lua.lock(); - let mut size = 0usize; - let buf = ffi::lua_tobuffer(lua.ref_thread(), ud.0.index, &mut size); - mlua_assert!(!buf.is_null(), "invalid Luau buffer"); - let buf = std::slice::from_raw_parts(buf as *const u8, size); - visitor.visit_bytes(buf) - }, + Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }), Value::Function(_) | Value::Thread(_) | Value::UserData(_) @@ -463,7 +456,7 @@ impl<'a> MapPairs<'a> { pub(crate) fn new(t: &'a Table, sort_keys: bool) -> Result { if sort_keys { let mut pairs = t.pairs::().collect::>>()?; - pairs.sort_by(|(a, _), (b, _)| b.cmp(a)); // reverse order as we pop values from the end + pairs.sort_by(|(a, _), (b, _)| b.sort_cmp(a)); // reverse order as we pop values from the end Ok(MapPairs::Vec(pairs)) } else { Ok(MapPairs::Iter(t.pairs::())) diff --git a/src/state.rs b/src/state.rs index cfcc81b..645556b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -31,7 +31,7 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] -use crate::chunk::Compiler; +use crate::{buffer::Buffer, chunk::Compiler}; #[cfg(feature = "async")] use { @@ -996,22 +996,21 @@ impl Lua { /// Requires `feature = "luau"` /// /// [buffer]: https://luau-lang.org/library#buffer-library - #[cfg(feature = "luau")] - pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { - use crate::types::SubtypeId; - + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + #[cfg(any(feature = "luau", doc))] + pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result { let lua = self.lock(); let state = lua.state(); unsafe { if lua.unlikely_memory_error() { crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?; - return Ok(AnyUserData(lua.pop_ref_thread(), SubtypeId::Buffer)); + return Ok(Buffer(lua.pop_ref_thread())); } let _sg = StackGuard::new(state); check_stack(state, 4)?; crate::util::push_buffer(state, buf.as_ref(), true)?; - Ok(AnyUserData(lua.pop_ref(), SubtypeId::Buffer)) + Ok(Buffer(lua.pop_ref())) } } diff --git a/src/state/raw.rs b/src/state/raw.rs index 61e70d3..2b20e45 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -551,6 +551,8 @@ impl RawLua { Value::Function(f) => self.push_ref(&f.0), Value::Thread(t) => self.push_ref(&t.0), Value::UserData(ud) => self.push_ref(&ud.0), + #[cfg(feature = "luau")] + Value::Buffer(buf) => self.push_ref(&buf.0), Value::Error(err) => { let protect = !self.unlikely_memory_error(); push_internal_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; @@ -652,9 +654,8 @@ impl RawLua { #[cfg(feature = "luau")] ffi::LUA_TBUFFER => { - // Buffer is represented as a userdata type ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer)) + Value::Buffer(crate::Buffer(self.pop_ref_thread())) } #[cfg(feature = "luajit")] diff --git a/src/table.rs b/src/table.rs index 002bfb4..5fb6f20 100644 --- a/src/table.rs +++ b/src/table.rs @@ -785,7 +785,7 @@ impl Table { // Collect key/value pairs into a vector so we can sort them let mut pairs = self.pairs::().flatten().collect::>(); // Sort keys - pairs.sort_by(|(a, _), (b, _)| a.cmp(b)); + pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b)); if pairs.is_empty() { return write!(fmt, "{{}}"); } diff --git a/src/types.rs b/src/types.rs index ad235f2..dee5631 100644 --- a/src/types.rs +++ b/src/types.rs @@ -32,8 +32,6 @@ pub type Number = ffi::lua_Number; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) enum SubtypeId { None, - #[cfg(feature = "luau")] - Buffer, #[cfg(feature = "luajit")] CData, } diff --git a/src/types/vector.rs b/src/types/vector.rs index f65ba86..3aa8d12 100644 --- a/src/types/vector.rs +++ b/src/types/vector.rs @@ -10,7 +10,7 @@ use super::LuaType; /// By default vectors are 3-dimensional, but can be 4-dimensional /// if the `luau-vector4` feature is enabled. #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct Vector(pub(crate) [f32; Self::SIZE]); impl fmt::Display for Vector { diff --git a/src/userdata.rs b/src/userdata.rs index 78e68be..e57e063 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -937,8 +937,6 @@ impl AnyUserData { pub(crate) fn type_name(&self) -> Result> { match self.1 { SubtypeId::None => {} - #[cfg(feature = "luau")] - SubtypeId::Buffer => return Ok(Some("buffer".to_owned())), #[cfg(feature = "luajit")] SubtypeId::CData => return Ok(Some("cdata".to_owned())), } @@ -1111,19 +1109,6 @@ impl Serialize for AnyUserData { S: Serializer, { let lua = self.0.lua.lock(); - - // Special case for Luau buffer type - #[cfg(feature = "luau")] - if self.1 == SubtypeId::Buffer { - let buf = unsafe { - let mut size = 0usize; - let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); - mlua_assert!(!buf.is_null(), "invalid Luau buffer"); - std::slice::from_raw_parts(buf as *const u8, size) - }; - return serializer.serialize_bytes(buf); - } - unsafe { let _ = lua .get_userdata_ref_type_id(&self.0) diff --git a/src/value.rs b/src/value.rs index 276904a..2b71890 100644 --- a/src/value.rs +++ b/src/value.rs @@ -48,7 +48,7 @@ pub enum Value { /// A Luau vector. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - Vector(crate::types::Vector), + Vector(crate::Vector), /// An interned string, managed by Lua. /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. @@ -60,8 +60,13 @@ pub enum Value { /// Reference to a Lua thread (or coroutine). Thread(Thread), /// Reference to a userdata object that holds a custom type which implements `UserData`. + /// /// Special builtin userdata types will be represented as other `Value` variants. UserData(AnyUserData), + /// A Luau buffer. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + Buffer(crate::Buffer), /// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned. Error(Box), } @@ -89,10 +94,10 @@ impl Value { Value::Function(_) => "function", Value::Thread(_) => "thread", Value::UserData(AnyUserData(_, SubtypeId::None)) => "userdata", - #[cfg(feature = "luau")] - Value::UserData(AnyUserData(_, SubtypeId::Buffer)) => "buffer", #[cfg(feature = "luajit")] Value::UserData(AnyUserData(_, SubtypeId::CData)) => "cdata", + #[cfg(feature = "luau")] + Value::Buffer(_) => "buffer", Value::Error(_) => "error", } } @@ -131,6 +136,8 @@ impl Value { | Value::Function(Function(r)) | Value::Thread(Thread(r, ..)) | Value::UserData(AnyUserData(r, ..)) => r.to_pointer(), + #[cfg(feature = "luau")] + Value::Buffer(crate::Buffer(r)) => r.to_pointer(), _ => ptr::null(), } } @@ -165,6 +172,8 @@ impl Value { })?; Ok(String(lua.pop_ref()).to_str()?.to_string()) }, + #[cfg(feature = "luau")] + Value::Buffer(buf) => StdString::from_utf8(buf.to_vec()).map_err(Error::external), Value::Error(err) => Ok(err.to_string()), } } @@ -416,15 +425,25 @@ impl Value { } } - /// Returns `true` if the value is a Buffer wrapped in [`AnyUserData`]. + /// Cast the value to a `Buffer`. + /// + /// If the value is `Buffer`, returns it or `None` otherwise. + #[cfg(any(feature = "luau", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] + #[inline] + pub fn as_buffer(&self) -> Option<&crate::Buffer> { + match self { + Value::Buffer(b) => Some(b), + _ => None, + } + } + + /// Returns `true` if the value is a `Buffer`. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - #[doc(hidden)] #[inline] pub fn is_buffer(&self) -> bool { - self.as_userdata() - .map(|ud| ud.1 == SubtypeId::Buffer) - .unwrap_or_default() + self.as_buffer().is_some() } /// Returns `true` if the value is a CData wrapped in [`AnyUserData`]. @@ -450,7 +469,7 @@ impl Value { // Compares two values. // Used to sort values for Debug printing. - pub(crate) fn cmp(&self, other: &Self) -> Ordering { + pub(crate) fn sort_cmp(&self, other: &Self) -> Ordering { fn cmp_num(a: Number, b: Number) -> Ordering { match (a, b) { _ if a < b => Ordering::Less, @@ -479,11 +498,14 @@ impl Value { (&Value::Number(a), &Value::Number(b)) => cmp_num(a, b), (Value::Integer(_) | Value::Number(_), _) => Ordering::Less, (_, Value::Integer(_) | Value::Number(_)) => Ordering::Greater, + // Vector (Luau) + #[cfg(feature = "luau")] + (Value::Vector(a), Value::Vector(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal), // String (Value::String(a), Value::String(b)) => a.as_bytes().cmp(&b.as_bytes()), (Value::String(_), _) => Ordering::Less, (_, Value::String(_)) => Ordering::Greater, - // Other variants can be randomly ordered + // Other variants can be ordered by their pointer (a, b) => a.to_pointer().cmp(&b.to_pointer()), } } @@ -520,6 +542,8 @@ impl Value { .unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer())); write!(fmt, "{s}") } + #[cfg(feature = "luau")] + buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()), Value::Error(e) if recursive => write!(fmt, "{e:?}"), Value::Error(_) => write!(fmt, "error"), } @@ -531,6 +555,7 @@ impl fmt::Debug for Value { if fmt.alternate() { return self.fmt_pretty(fmt, true, 0, &mut HashSet::new()); } + match self { Value::Nil => write!(fmt, "Nil"), Value::Boolean(b) => write!(fmt, "Boolean({b})"), @@ -544,6 +569,8 @@ impl fmt::Debug for Value { Value::Function(f) => write!(fmt, "{f:?}"), Value::Thread(t) => write!(fmt, "{t:?}"), Value::UserData(ud) => write!(fmt, "{ud:?}"), + #[cfg(feature = "luau")] + Value::Buffer(buf) => write!(fmt, "{buf:?}"), Value::Error(e) => write!(fmt, "Error({e:?})"), } } @@ -566,6 +593,8 @@ impl PartialEq for Value { (Value::Function(a), Value::Function(b)) => a == b, (Value::Thread(a), Value::Thread(b)) => a == b, (Value::UserData(a), Value::UserData(b)) => a == b, + #[cfg(feature = "luau")] + (Value::Buffer(a), Value::Buffer(b)) => a == b, _ => false, } } @@ -674,6 +703,8 @@ impl<'a> Serialize for SerializableValue<'a> { Value::UserData(ud) if ud.is_serializable() || self.options.deny_unsupported_types => { ud.serialize(serializer) } + #[cfg(feature = "luau")] + Value::Buffer(buf) => buf.serialize(serializer), Value::Function(_) | Value::Thread(_) | Value::UserData(_) diff --git a/tests/buffer.rs b/tests/buffer.rs new file mode 100644 index 0000000..4fb9502 --- /dev/null +++ b/tests/buffer.rs @@ -0,0 +1,56 @@ +#![cfg(feature = "luau")] + +use mlua::{Lua, Result, Value}; + +#[test] +fn test_buffer() -> Result<()> { + let lua = Lua::new(); + + let buf1 = lua + .load( + r#" + local buf = buffer.fromstring("hello") + assert(buffer.len(buf) == 5) + return buf + "#, + ) + .eval::()?; + assert!(buf1.is_buffer()); + assert_eq!(buf1.type_name(), "buffer"); + + let buf2 = lua.load("buffer.fromstring('hello')").eval::()?; + assert_ne!(buf1, buf2); + + // Check that we can pass buffer type to Lua + let buf1 = buf1.as_buffer().unwrap(); + let func = lua.create_function(|_, buf: Value| return buf.to_string())?; + assert_eq!(func.call::(buf1)?, "hello"); + + // Check buffer methods + assert_eq!(buf1.len(), 5); + assert_eq!(buf1.to_vec(), b"hello"); + assert_eq!(buf1.read_bytes::<3>(1), [b'e', b'l', b'l']); + buf1.write_bytes(1, b"i"); + assert_eq!(buf1.to_vec(), b"hillo"); + + let buf3 = lua.create_buffer(b"")?; + assert!(buf3.is_empty()); + + Ok(()) +} + +#[test] +#[should_panic(expected = "range end index 14 out of range for slice of length 13")] +fn test_buffer_out_of_bounds_read() { + let lua = Lua::new(); + let buf = lua.create_buffer(b"hello, world!").unwrap(); + _ = buf.read_bytes::<1>(13); +} + +#[test] +#[should_panic(expected = "range end index 16 out of range for slice of length 13")] +fn test_buffer_out_of_bounds_write() { + let lua = Lua::new(); + let buf = lua.create_buffer(b"hello, world!").unwrap(); + buf.write_bytes(14, b"!!"); +} diff --git a/tests/conversion.rs b/tests/conversion.rs index 5941879..5ca9982 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -384,8 +384,8 @@ fn test_bstring_from_lua() -> Result<()> { fn test_bstring_from_lua_buffer() -> Result<()> { let lua = Lua::new(); - let b = lua.create_buffer("hello, world")?; - let bstr = lua.unpack::(Value::UserData(b))?; + let buf = lua.create_buffer("hello, world")?; + let bstr = lua.convert::(buf)?; assert_eq!(bstr, "hello, world"); // Test from stack diff --git a/tests/luau.rs b/tests/luau.rs index 3d7268b..2c24c17 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -465,32 +465,6 @@ fn test_coverage() -> Result<()> { Ok(()) } -#[test] -fn test_buffer() -> Result<()> { - let lua = Lua::new(); - - let buf1 = lua - .load( - r#" - local buf = buffer.fromstring("hello") - assert(buffer.len(buf) == 5) - return buf - "#, - ) - .eval::()?; - assert!(buf1.is_userdata() && buf1.is_buffer()); - assert_eq!(buf1.type_name(), "buffer"); - - let buf2 = lua.load("buffer.fromstring('hello')").eval::()?; - assert_ne!(buf1, buf2); - - // Check that we can pass buffer type to Lua - let func = lua.create_function(|_, buf: Value| return buf.to_string())?; - assert!(func.call::(buf1)?.starts_with("buffer:")); - - Ok(()) -} - #[test] fn test_fflags() { // We cannot really on any particular feature flag to be present diff --git a/tests/serde.rs b/tests/serde.rs index fb50c04..d3ed372 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -717,27 +717,29 @@ fn test_arbitrary_precision() { #[cfg(feature = "luau")] #[test] -fn test_buffer_serialize() { +fn test_buffer_serialize() -> LuaResult<()> { let lua = Lua::new(); - let buf = lua.create_buffer(&[1, 2, 3, 4]).unwrap(); + 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(&[]).unwrap(); + 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() { +fn test_buffer_from_value() -> LuaResult<()> { let lua = Lua::new(); - let buf = lua.create_buffer(&[1, 2, 3, 4]).unwrap(); - let val = lua - .from_value::(Value::UserData(buf)) - .unwrap(); + 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(()) }