Skip to content

Commit 1d578bd

Browse files
committed
Add json module
1 parent bbbe5a0 commit 1d578bd

File tree

9 files changed

+447
-3
lines changed

9 files changed

+447
-3
lines changed

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ lua52 = ["mlua/lua52"]
1313
lua53 = ["mlua/lua53"]
1414
lua54 = ["mlua/lua54"]
1515
luau = ["mlua/luau"]
16+
send = ["mlua/send"]
17+
vendored = ["mlua/vendored"]
18+
19+
json = ["mlua/serde", "dep:ouroboros", "dep:serde", "dep:serde_json"]
1620

1721
[dependencies]
1822
mlua = { version = "0.11" }
23+
ouroboros = { version = "0.18", optional = true }
24+
serde = { version = "1.0", optional = true }
25+
serde_json = { version = "1.0", optional = true }
1926
owo-colors = "4"

lua/testing.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ function Testing:_print_results()
211211
failed,
212212
skipped,
213213
total,
214-
duration
214+
tostring(duration)
215215
)
216216
println()
217217
println(prefix, stats)

src/bytes.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::ops::Deref;
2+
3+
use mlua::{BorrowedBytes, Error, FromLua, Lua, Result, String as LuaString, UserData, UserDataRef, Value};
4+
5+
use crate::types::MaybeSend;
6+
7+
/// A wrapper around a byte slice that can be passed to Lua as userdata.
8+
#[cfg(not(feature = "send"))]
9+
pub struct BytesBox(Box<dyn AsRef<[u8]>>);
10+
11+
/// A wrapper around a byte slice that can be passed to Lua as userdata.
12+
#[cfg(feature = "send")]
13+
pub struct BytesBox(Box<dyn AsRef<[u8]> + Send>);
14+
15+
impl<T: AsRef<[u8]> + MaybeSend + 'static> From<T> for BytesBox {
16+
#[inline(always)]
17+
fn from(value: T) -> Self {
18+
Self(Box::new(value))
19+
}
20+
}
21+
22+
impl UserData for BytesBox {}
23+
24+
pub(crate) enum StringOrBytes {
25+
String(LuaString),
26+
Bytes(UserDataRef<BytesBox>),
27+
}
28+
29+
impl FromLua for StringOrBytes {
30+
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
31+
match value {
32+
Value::String(s) => Ok(Self::String(s)),
33+
Value::UserData(ud) => Ok(Self::Bytes(ud.borrow::<BytesBox>()?)),
34+
_ => Err(Error::FromLuaConversionError {
35+
from: value.type_name(),
36+
to: "string or bytes".into(),
37+
message: None,
38+
}),
39+
}
40+
}
41+
}
42+
43+
impl StringOrBytes {
44+
#[inline]
45+
pub(crate) fn as_bytes_deref(&self) -> impl Deref<Target = [u8]> {
46+
match self {
47+
StringOrBytes::String(s) => AsBytesRefImpl::Lua(s.as_bytes()),
48+
StringOrBytes::Bytes(b) => AsBytesRefImpl::Ref((*b.0).as_ref()),
49+
}
50+
}
51+
}
52+
53+
enum AsBytesRefImpl<'a> {
54+
Ref(&'a [u8]),
55+
Lua(BorrowedBytes<'a>),
56+
}
57+
58+
impl Deref for AsBytesRefImpl<'_> {
59+
type Target = [u8];
60+
61+
#[inline]
62+
fn deref(&self) -> &Self::Target {
63+
match self {
64+
Self::Ref(b) => b,
65+
Self::Lua(s) => s.as_ref(),
66+
}
67+
}
68+
}

src/json.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
use std::result::Result as StdResult;
2+
use std::sync::Arc;
3+
4+
use mlua::{
5+
AnyUserData, Error as LuaError, ExternalResult, Function, Integer as LuaInteger, IntoLuaMulti, Lua,
6+
LuaSerdeExt, MetaMethod, MultiValue, Result, String as LuaString, Table, UserData, UserDataMethods,
7+
UserDataRefMut, Value,
8+
};
9+
use ouroboros::self_referencing;
10+
use serde::{Serialize, Serializer};
11+
12+
use crate::bytes::StringOrBytes;
13+
14+
/// Represents a native Json object in Lua.
15+
#[derive(Clone)]
16+
pub(crate) struct JsonObject {
17+
root: Arc<serde_json::Value>,
18+
current: *const serde_json::Value,
19+
}
20+
21+
impl Serialize for JsonObject {
22+
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
23+
self.current().serialize(serializer)
24+
}
25+
}
26+
27+
impl JsonObject {
28+
/// Creates a new `JsonObject` from the given JSON value.
29+
///
30+
/// SAFETY:
31+
/// The caller must ensure that `current` is a value inside `root`.
32+
unsafe fn new(root: &Arc<serde_json::Value>, current: &serde_json::Value) -> Self {
33+
let root = root.clone();
34+
JsonObject { root, current }
35+
}
36+
37+
/// Returns a reference to the current JSON value.
38+
#[inline(always)]
39+
fn current(&self) -> &serde_json::Value {
40+
unsafe { &*self.current }
41+
}
42+
43+
/// Returns a new `JsonObject` which points to the value at the given key.
44+
///
45+
/// This operation is cheap and does not clone the underlying data.
46+
fn get(&self, key: Value) -> Option<Self> {
47+
let value = match key {
48+
Value::Integer(index) if index > 0 => self.current().get(index as usize - 1),
49+
Value::String(key) => key.to_str().ok().and_then(|s| self.current().get(&*s)),
50+
_ => None,
51+
}?;
52+
unsafe { Some(Self::new(&self.root, value)) }
53+
}
54+
55+
/// Returns a new `JsonObject` by following the given JSON Pointer path.
56+
fn pointer(&self, path: &str) -> Option<Self> {
57+
unsafe { Some(JsonObject::new(&self.root, self.root.pointer(path)?)) }
58+
}
59+
60+
/// Converts this `JsonObject` into a Lua `Value`.
61+
fn into_lua(self, lua: &Lua) -> Result<Value> {
62+
match self.current() {
63+
serde_json::Value::Null => Ok(Value::NULL),
64+
serde_json::Value::Bool(b) => Ok(Value::Boolean(*b)),
65+
serde_json::Value::Number(n) => {
66+
if let Some(n) = n.as_i64() {
67+
Ok(Value::Integer(n as _))
68+
} else if let Some(n) = n.as_f64() {
69+
Ok(Value::Number(n))
70+
} else {
71+
Err(LuaError::ToLuaConversionError {
72+
from: "number".to_string(),
73+
to: "integer or float",
74+
message: Some("number is too big to fit in a Lua integer".to_owned()),
75+
})
76+
}
77+
}
78+
serde_json::Value::String(s) => Ok(Value::String(lua.create_string(s)?)),
79+
value @ serde_json::Value::Array(_) | value @ serde_json::Value::Object(_) => {
80+
let obj_ud = lua.create_ser_userdata(unsafe { JsonObject::new(&self.root, value) })?;
81+
Ok(Value::UserData(obj_ud))
82+
}
83+
}
84+
}
85+
86+
fn lua_iterator(&self, lua: &Lua) -> Result<MultiValue> {
87+
match self.current() {
88+
serde_json::Value::Array(_) => {
89+
let next = Self::lua_array_iterator(lua)?;
90+
let iter_ud = AnyUserData::wrap(LuaJsonArrayIter {
91+
value: self.clone(),
92+
next: 1, // index starts at 1
93+
});
94+
(next, iter_ud).into_lua_multi(lua)
95+
}
96+
serde_json::Value::Object(_) => {
97+
let next = Self::lua_map_iterator(lua)?;
98+
let iter_builder = LuaJsonMapIterBuilder {
99+
value: self.clone(),
100+
iter_builder: |value| value.current().as_object().unwrap().iter(),
101+
};
102+
let iter_ud = AnyUserData::wrap(iter_builder.build());
103+
(next, iter_ud).into_lua_multi(lua)
104+
}
105+
_ => ().into_lua_multi(lua),
106+
}
107+
}
108+
109+
/// Returns an iterator function for arrays.
110+
fn lua_array_iterator(lua: &Lua) -> Result<Function> {
111+
if let Ok(Some(f)) = lua.named_registry_value("__json_array_iterator") {
112+
return Ok(f);
113+
}
114+
115+
let f = lua.create_function(|lua, mut it: UserDataRefMut<LuaJsonArrayIter>| {
116+
it.next += 1;
117+
match it.value.get(Value::Integer(it.next - 1)) {
118+
Some(next_value) => (it.next - 1, next_value.into_lua(lua)?).into_lua_multi(lua),
119+
None => ().into_lua_multi(lua),
120+
}
121+
})?;
122+
lua.set_named_registry_value("__json_array_iterator", &f)?;
123+
Ok(f)
124+
}
125+
126+
/// Returns an iterator function for objects.
127+
fn lua_map_iterator(lua: &Lua) -> Result<Function> {
128+
if let Ok(Some(f)) = lua.named_registry_value("__json_map_iterator") {
129+
return Ok(f);
130+
}
131+
132+
let f = lua.create_function(|lua, mut it: UserDataRefMut<LuaJsonMapIter>| {
133+
let root = it.borrow_value().root.clone();
134+
it.with_iter_mut(move |iter| match iter.next() {
135+
Some((key, value)) => {
136+
let key = lua.create_string(key)?;
137+
let value = unsafe { JsonObject::new(&root, value) }.into_lua(lua)?;
138+
(key, value).into_lua_multi(lua)
139+
}
140+
None => ().into_lua_multi(lua),
141+
})
142+
})?;
143+
lua.set_named_registry_value("__json_map_iterator", &f)?;
144+
Ok(f)
145+
}
146+
}
147+
148+
impl From<serde_json::Value> for JsonObject {
149+
fn from(value: serde_json::Value) -> Self {
150+
let root = Arc::new(value);
151+
unsafe { Self::new(&root, &root) }
152+
}
153+
}
154+
155+
impl UserData for JsonObject {
156+
fn register(registry: &mut mlua::UserDataRegistry<Self>) {
157+
registry.add_method("pointer", |lua, this, path: LuaString| {
158+
this.pointer(&path.to_str()?)
159+
.map(|obj| obj.into_lua(lua))
160+
.unwrap_or(Ok(Value::Nil))
161+
});
162+
163+
registry.add_method("dump", |lua, this, ()| lua.to_value(this));
164+
165+
registry.add_method("iter", |lua, this, ()| this.lua_iterator(lua));
166+
167+
registry.add_meta_method(MetaMethod::Index, |lua, this, key: Value| {
168+
this.get(key)
169+
.map(|obj| obj.into_lua(lua))
170+
.unwrap_or(Ok(Value::Nil))
171+
});
172+
173+
registry.add_meta_method(crate::METAMETHOD_ITER, |lua, this, ()| this.lua_iterator(lua));
174+
}
175+
}
176+
177+
struct LuaJsonArrayIter {
178+
value: JsonObject,
179+
next: LuaInteger,
180+
}
181+
182+
#[self_referencing]
183+
struct LuaJsonMapIter {
184+
value: JsonObject,
185+
186+
#[borrows(value)]
187+
#[covariant]
188+
iter: serde_json::map::Iter<'this>,
189+
}
190+
191+
fn decode(lua: &Lua, data: StringOrBytes) -> Result<StdResult<Value, String>> {
192+
let json: serde_json::Value = lua_try!(serde_json::from_slice(&data.as_bytes_deref()).into_lua_err());
193+
Ok(Ok(lua.to_value(&json)?))
194+
}
195+
196+
fn decode_native(lua: &Lua, data: StringOrBytes) -> Result<StdResult<Value, String>> {
197+
let json: serde_json::Value = lua_try!(serde_json::from_slice(&data.as_bytes_deref()).into_lua_err());
198+
Ok(Ok(lua_try!(JsonObject::from(json).into_lua(lua))))
199+
}
200+
201+
fn encode(value: Value, options: Option<Table>) -> StdResult<String, String> {
202+
let mut value = value.to_serializable();
203+
let options = options.as_ref();
204+
205+
if options.and_then(|t| t.get::<bool>("relaxed").ok()) == Some(true) {
206+
value = value.deny_recursive_tables(false).deny_unsupported_types(false);
207+
}
208+
209+
if options.and_then(|t| t.get::<bool>("pretty").ok()) == Some(true) {
210+
value = value.sort_keys(true);
211+
return serde_json::to_string_pretty(&value).map_err(|e| e.to_string());
212+
}
213+
214+
serde_json::to_string(&value).map_err(|e| e.to_string())
215+
}
216+
217+
/// A loader for the `json` module.
218+
fn loader(lua: &Lua) -> Result<Table> {
219+
let t = lua.create_table()?;
220+
t.set("decode", lua.create_function(decode)?)?;
221+
t.set("decode_native", lua.create_function(decode_native)?)?;
222+
t.set("encode", Function::wrap_raw(encode))?;
223+
Ok(t)
224+
}
225+
226+
/// Registers the `json` module in the given Lua state.
227+
pub fn register(lua: &Lua, name: Option<&str>) -> Result<Table> {
228+
let name = name.unwrap_or("@json");
229+
let value = loader(lua)?;
230+
lua.register_module(name, &value)?;
231+
Ok(value)
232+
}

src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
#[rustfmt::skip]
2+
#[allow(unused)]
3+
pub(crate) const METAMETHOD_ITER: &str = if cfg!(feature = "luau") { "__iter" } else { "__pairs" };
4+
5+
#[macro_use]
6+
mod macros;
7+
mod types;
8+
19
pub(crate) mod terminal;
210
pub(crate) mod time;
311

412
pub mod assertions;
13+
pub mod bytes;
514
pub mod testing;
15+
16+
#[cfg(feature = "json")]
17+
pub mod json;

src/macros.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
macro_rules! lua_try {
2+
($result:expr) => {
3+
match $result {
4+
Ok(ok) => ok,
5+
Err(err) => return Ok(Err(format!("{err:#}"))),
6+
}
7+
};
8+
}

src/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// A trait that adds `Send` requirement if `send` feature is enabled.
2+
#[cfg(feature = "send")]
3+
pub trait MaybeSend: Send {}
4+
#[cfg(feature = "send")]
5+
impl<T: Send> MaybeSend for T {}
6+
7+
#[cfg(not(feature = "send"))]
8+
pub trait MaybeSend {}
9+
#[cfg(not(feature = "send"))]
10+
impl<T> MaybeSend for T {}

tests/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ fn run_file(modname: &str) -> Result<()> {
1111
mlua_stdlib::assertions::register(&lua, None)?;
1212
let testing = mlua_stdlib::testing::register(&lua, None)?;
1313

14+
#[cfg(feature = "json")]
15+
mlua_stdlib::json::register(&lua, None)?;
16+
1417
// Add `testing` global variable (an instance of the testing framework)
1518
let testing = testing.call_function::<Table>("new", modname)?;
1619
lua.globals().set("testing", &testing)?;
@@ -29,14 +32,21 @@ fn run_file(modname: &str) -> Result<()> {
2932
}
3033

3134
macro_rules! include_tests {
32-
($($name:ident, )*) => { $(
35+
($($name:ident,)*) => {
36+
$(
3337
#[test]
3438
fn $name() -> Result<()> {
3539
run_file(stringify!($name))
3640
}
37-
)*}
41+
)*
42+
};
43+
44+
($name:ident) => { include_tests! { $name, } };
3845
}
3946

4047
include_tests! {
4148
assertions,
4249
}
50+
51+
#[cfg(feature = "json")]
52+
include_tests!(json);

0 commit comments

Comments
 (0)