Cuberite
A lightweight, fast and extensible game server for Minecraft
LuaJson.cpp
Go to the documentation of this file.
1 
2 // LuaJson.cpp
3 
4 // Implements the Json exposure bindings to Lua
5 
6 #include "Globals.h"
7 #include <sstream>
8 #include "LuaJson.h"
9 #include "LuaState.h"
10 #include "tolua++/include/tolua++.h"
11 #include "json/json.h"
12 #include "../JsonUtils.h"
13 
14 
15 
16 
17 
18 // fwd:
19 
20 static void PushJsonValue(const Json::Value & a_Value, cLuaState & a_LuaState);
21 static Json::Value JsonSerializeValue(cLuaState & a_LuaState);
22 
23 
24 
25 
30  public std::runtime_error
31 {
32  using Super = std::runtime_error;
33 
34 public:
36  explicit CannotSerializeException(const AString & a_ValueName, const char * a_ErrorMsg):
37  Super(a_ErrorMsg),
38  m_ValueName(a_ValueName)
39  {
40  }
41 
43  explicit CannotSerializeException(int a_ValueIndex, const char * a_ErrorMsg):
44  Super(a_ErrorMsg),
45  m_ValueName(fmt::format(FMT_STRING("{}"), a_ValueIndex))
46  {
47  }
48 
51  explicit CannotSerializeException(const CannotSerializeException & a_Parent, const AString & a_ValueNamePrefix):
52  Super(a_Parent.what()),
53  m_ValueName(a_ValueNamePrefix + "." + a_Parent.m_ValueName)
54  {
55  }
56 
59  explicit CannotSerializeException(const CannotSerializeException & a_Parent, int a_ValueNamePrefixIndex):
60  Super(a_Parent.what()),
61  m_ValueName(fmt::format(FMT_STRING("{}"), a_ValueNamePrefixIndex) + "." + a_Parent.m_ValueName)
62  {
63  }
64 
65  const AString & GetValueName() const { return m_ValueName; }
66 
67 protected:
69 };
70 
71 
72 
73 
74 
77 static void PushJsonArray(const Json::Value & a_Value, cLuaState & a_LuaState)
78 {
79  // Create the appropriately-sized Lua table:
80  lua_createtable(a_LuaState, static_cast<int>(a_Value.size()), 0);
81 
82  // Insert each value to the appropriate index (1-based):
83  int idx = 1;
84  for (const auto & v: a_Value)
85  {
86  // Include Json null values in the array - it will have holes, but indices will stay the same
87  PushJsonValue(v, a_LuaState);
88  lua_rawseti(a_LuaState, -2, idx);
89  idx += 1;
90  } // for v: a_Value[]
91 }
92 
93 
94 
95 
96 
99 static void PushJsonObject(const Json::Value & a_Value, cLuaState & a_LuaState)
100 {
101  // Create the appropriately-sized Lua table:
102  lua_createtable(a_LuaState, 0, static_cast<int>(a_Value.size()));
103 
104  // Json::Value has no means of iterating over children with their names included.
105  // We need to iterate over names and "find" them in the object again:
106  auto names = a_Value.getMemberNames();
107  for (const auto & n: names)
108  {
109  auto v = a_Value[n];
110  if (v.isNull())
111  {
112  // Skip null values
113  continue;
114  }
115 
116  // Set the value in Lua's table:
117  a_LuaState.Push(n);
118  PushJsonValue(v, a_LuaState);
119  lua_rawset(a_LuaState, -3);
120  } // for itr - a_Value[]
121 }
122 
123 
124 
125 
126 
128 void PushJsonValue(const Json::Value & a_Value, cLuaState & a_LuaState)
129 {
130  switch (a_Value.type())
131  {
132  case Json::nullValue:
133  {
134  a_LuaState.Push(cLuaState::Nil);
135  break;
136  }
137 
138  case Json::intValue:
139  case Json::uintValue:
140  case Json::realValue:
141  {
142  a_LuaState.Push(static_cast<lua_Number>(a_Value.asDouble()));
143  break;
144  }
145 
146  case Json::booleanValue:
147  {
148  a_LuaState.Push(a_Value.asBool());
149  break;
150  }
151 
152  case Json::stringValue:
153  {
154  a_LuaState.Push(a_Value.asString());
155  break;
156  }
157 
158  case Json::arrayValue:
159  {
160  PushJsonArray(a_Value, a_LuaState);
161  break;
162  }
163 
164  case Json::objectValue:
165  {
166  PushJsonObject(a_Value, a_LuaState);
167  break;
168  }
169  } // switch (v.type())
170 }
171 
172 
173 
174 
175 
178 static Json::Value JsonSerializeTable(cLuaState & a_LuaState)
179 {
180  Json::Value res;
181  lua_pushnil(a_LuaState);
182  while (lua_next(a_LuaState, -2) != 0)
183  {
184  if (lua_type(a_LuaState, -2) == LUA_TNUMBER)
185  {
186  int idx;
187  a_LuaState.GetStackValue(-2, idx);
188  try
189  {
190  res[idx - 1] = JsonSerializeValue(a_LuaState);
191  }
192  catch (const CannotSerializeException & exc)
193  {
194  throw CannotSerializeException(exc, idx);
195  }
196  catch (const std::exception & exc) // Cannot catch Json::Exception, because it's not properly defined
197  {
198  throw CannotSerializeException(idx, exc.what());
199  }
200  }
201  else
202  {
203  AString name;
204  if (a_LuaState.GetStackValue(-2, name))
205  {
206  try
207  {
208  res[name] = JsonSerializeValue(a_LuaState);
209  }
210  catch (const CannotSerializeException & exc)
211  {
212  throw CannotSerializeException(exc, name);
213  }
214  catch (const std::exception & exc) // Cannot catch Json::Exception, because it's not properly defined
215  {
216  throw CannotSerializeException(name, exc.what());
217  }
218  }
219  }
220  lua_pop(a_LuaState, 1);
221  }
222  return res;
223 }
224 
225 
226 
227 
228 
230 static Json::Value JsonSerializeValue(cLuaState & a_LuaState)
231 {
232  switch (lua_type(a_LuaState, -1))
233  {
234  case LUA_TBOOLEAN:
235  {
236  bool v;
237  a_LuaState.GetStackValue(-1, v);
238  return Json::Value(v);
239  }
240  case LUA_TNIL:
241  {
242  return Json::Value(Json::nullValue);
243  }
244  case LUA_TNUMBER:
245  {
246  lua_Number v;
247  a_LuaState.GetStackValue(-1, v);
248  return Json::Value(v);
249  }
250  case LUA_TSTRING:
251  {
252  AString v;
253  a_LuaState.GetStackValue(-1, v);
254  return Json::Value(v);
255  }
256  case LUA_TTABLE:
257  {
258  return JsonSerializeTable(a_LuaState);
259  }
260  default:
261  {
262  LOGD("Attempting to serialize an unhandled Lua value type: %d", lua_type(a_LuaState, -1));
263  return Json::Value(Json::nullValue);
264  }
265  }
266 }
267 
268 
269 
270 
271 
272 static int tolua_cJson_Parse(lua_State * a_LuaState)
273 {
274  // Function signature:
275  // cJson:Parse("string") -> table
276 
277  // Check the param types:
278  cLuaState L(a_LuaState);
279  if (
280  !L.CheckParamUserTable(1, "cJson") ||
281  !L.CheckParamString(2) ||
282  !L.CheckParamEnd(3)
283  )
284  {
285  return 0;
286  }
287 
288  // Get the input string:
289  AString input;
290  if (!L.GetStackValue(2, input))
291  {
292  LOGWARNING("cJson:Parse(): Cannot read input string");
293  L.LogStackTrace();
294  return 0;
295  }
296 
297  // Parse the string:
298  Json::Value root;
299  AString ParseError;
300  if (!JsonUtils::ParseString(input, root, &ParseError))
301  {
302  L.Push(cLuaState::Nil, fmt::format(FMT_STRING("Parsing Json failed: {}"), ParseError));
303  return 2;
304  }
305 
306  // Push the Json value onto Lua stack:
307  PushJsonValue(root, L);
308  return 1;
309 }
310 
311 
312 
313 
314 
315 static int tolua_cJson_Serialize(lua_State * a_LuaState)
316 {
317  // Function signature:
318  // cJson:Serialize({table}, [{option1 = value1, option2 = value2}]) -> string
319 
320  // Check the param types:
321  cLuaState L(a_LuaState);
322  if (
323  !L.CheckParamUserTable(1, "cJson") ||
324  !L.CheckParamTable(2) ||
325  !L.CheckParamEnd(4)
326  )
327  {
328  return 0;
329  }
330 
331  // Push the table to the top of the Lua stack, and call the serializing function:
332  lua_pushvalue(L, 2);
333  Json::Value root;
334  try
335  {
336  root = JsonSerializeValue(L);
337  }
338  catch (const CannotSerializeException & exc)
339  {
340  lua_pushnil(L);
341  L.Push(fmt::format(
342  FMT_STRING("Cannot serialize into Json, value \"{}\" caused an error \"{}\""),
343  exc.GetValueName(), exc.what()
344  ));
345  return 2;
346  }
347  lua_pop(L, 1);
348 
349  // Create the writer, with all properties (optional param 3) applied to it:
350  Json::StreamWriterBuilder builder;
351  if (lua_istable(L, 3))
352  {
353  lua_pushnil(L);
354  while (lua_next(L, -2) != 0)
355  {
356  if (lua_type(L, -2) == LUA_TSTRING)
357  {
358  AString propName, propValue;
359  if (L.GetStackValues(-2, propName, propValue))
360  {
361  builder[propName] = propValue;
362  }
363  }
364  lua_pop(L, 1);
365  }
366  // Check for invalid settings:
367  Json::Value invalid;
368  if (!builder.validate(&invalid))
369  {
370  LOGINFO("cJson:Serialize(): detected invalid settings:");
371  for (const auto & n: invalid.getMemberNames())
372  {
373  LOGINFO(" \"%s\" (\"%s\")", n.c_str(), invalid[n].asCString());
374  }
375  }
376  }
377  auto writer(builder.newStreamWriter());
378 
379  // Serialize the string and push it as the return value:
380  std::stringstream ss;
381  writer->write(root, &ss);
382  L.Push(ss.str());
383  return 1;
384 }
385 
386 
387 
388 
389 
390 void cLuaJson::Bind(cLuaState & a_LuaState)
391 {
392  tolua_beginmodule(a_LuaState, nullptr);
393 
394  // Create the cJson API class:
395  tolua_usertype(a_LuaState, "cJson");
396  tolua_cclass(a_LuaState, "cJson", "cJson", "", nullptr);
397 
398  // Fill in the functions (alpha-sorted):
399  tolua_beginmodule(a_LuaState, "cJson");
400  tolua_function(a_LuaState, "Parse", tolua_cJson_Parse);
401  tolua_function(a_LuaState, "Serialize", tolua_cJson_Serialize);
402  tolua_endmodule(a_LuaState);
403  tolua_endmodule(a_LuaState);
404 }
405 
406 
407 
408 
static int tolua_cJson_Serialize(lua_State *a_LuaState)
Definition: LuaJson.cpp:315
static void PushJsonObject(const Json::Value &a_Value, cLuaState &a_LuaState)
Pushes the specified Json object as a table on top of the specified Lua state.
Definition: LuaJson.cpp:99
static Json::Value JsonSerializeValue(cLuaState &a_LuaState)
Serializes the Lua value at the top of the specified Lua state into a Json value.
Definition: LuaJson.cpp:230
static void PushJsonArray(const Json::Value &a_Value, cLuaState &a_LuaState)
Pushes the specified Json array as a table on top of the specified Lua state.
Definition: LuaJson.cpp:77
static Json::Value JsonSerializeTable(cLuaState &a_LuaState)
Serializes the Lua table at the top of the specified Lua state's stack into a Json value.
Definition: LuaJson.cpp:178
static int tolua_cJson_Parse(lua_State *a_LuaState)
Definition: LuaJson.cpp:272
static void PushJsonValue(const Json::Value &a_Value, cLuaState &a_LuaState)
Pushes the specified Json value as an appropriate type on top of the specified Lua state.
Definition: LuaJson.cpp:128
void LOGWARNING(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:67
#define LOGD
Definition: LoggerSimple.h:83
void LOGINFO(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:61
std::string AString
Definition: StringUtils.h:11
Implements custom fmtlib formatting for cChunkCoords.
Definition: ChunkDef.h:104
bool ParseString(const AString &a_JsonStr, Json::Value &a_Root, AString *a_ErrorMsg)
Definition: JsonUtils.cpp:34
Exception thrown when the input cannot be serialized.
Definition: LuaJson.cpp:31
std::runtime_error Super
Definition: LuaJson.cpp:32
CannotSerializeException(const CannotSerializeException &a_Parent, int a_ValueNamePrefixIndex)
Constructs a new instance of the exception that takes the error message and value name from the paren...
Definition: LuaJson.cpp:59
CannotSerializeException(int a_ValueIndex, const char *a_ErrorMsg)
Constructs a new instance of the exception based on the provided values directly.
Definition: LuaJson.cpp:43
CannotSerializeException(const CannotSerializeException &a_Parent, const AString &a_ValueNamePrefix)
Constructs a new instance of the exception that takes the error message and value name from the paren...
Definition: LuaJson.cpp:51
const AString & GetValueName() const
Definition: LuaJson.cpp:65
CannotSerializeException(const AString &a_ValueName, const char *a_ErrorMsg)
Constructs a new instance of the exception based on the provided values directly.
Definition: LuaJson.cpp:36
static void Bind(cLuaState &a_LuaState)
Registers the Json library in the specified Lua state.
Definition: LuaJson.cpp:390
Encapsulates a Lua state and provides some syntactic sugar for common operations.
Definition: LuaState.h:56
void Push(Arg1 &&a_Arg1, Arg2 &&a_Arg2, Args &&... a_Args)
Pushes multiple arguments onto the Lua stack.
Definition: LuaState.h:604
void LogStackTrace(int a_StartingDepth=0)
Logs all items in the current stack trace to the server console.
Definition: LuaState.cpp:2134
bool CheckParamEnd(int a_Param)
Returns true if the specified parameter on the stack is nil (indicating an end-of-parameters)
Definition: LuaState.cpp:1998
bool CheckParamString(int a_StartParam, int a_EndParam=-1)
Returns true if the specified parameters on the stack are strings; also logs warning if not.
Definition: LuaState.cpp:1829
bool CheckParamUserTable(int a_StartParam, const char *a_UserTable, int a_EndParam=-1)
Returns true if the specified parameters on the stack are of the specified usertable type; also logs ...
Definition: LuaState.cpp:1661
static const cNil Nil
Definition: LuaState.h:452
bool CheckParamTable(int a_StartParam, int a_EndParam=-1)
Returns true if the specified parameters on the stack are tables; also logs warning if not.
Definition: LuaState.cpp:1727
bool GetStackValue(int a_StackPos, AString &a_Value)
Definition: LuaState.cpp:1119
bool GetStackValues(int a_StartStackPos, Arg1 &&a_Arg1, Args &&... args)
Retrieves a list of values from the Lua stack, starting at the specified index.
Definition: LuaState.h:766