mirror of https://github.com/OpenTTD/OpenTTD
Codechange: keep how we convert string <-> JSON private (#11269)
parent
d725fa14a2
commit
00f13282a9
|
@ -14,9 +14,22 @@
|
|||
#include "../script_instance.hpp"
|
||||
#include "../../string_func.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/* static */ bool ScriptAdmin::MakeJSON(nlohmann::json &json, HSQUIRRELVM vm, SQInteger index, int depth)
|
||||
/**
|
||||
* Convert a Squirrel structure into a JSON object.
|
||||
*
|
||||
* This function is not "static", so it can be tested in unittests.
|
||||
*
|
||||
* @param json The resulting JSON object.
|
||||
* @param vm The VM to operate on.
|
||||
* @param index The index we are currently working for.
|
||||
* @param depth The current depth in the squirrel struct.
|
||||
* @return True iff the conversion was successful.
|
||||
*/
|
||||
bool ScriptAdminMakeJSON(nlohmann::json &json, HSQUIRRELVM vm, SQInteger index, int depth = 0)
|
||||
{
|
||||
if (depth == SQUIRREL_MAX_DEPTH) {
|
||||
ScriptLog::Error("Send parameters can only be nested to 25 deep. No data sent."); // SQUIRREL_MAX_DEPTH = 25
|
||||
|
@ -47,7 +60,7 @@
|
|||
while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
|
||||
nlohmann::json tmp;
|
||||
|
||||
bool res = MakeJSON(tmp, vm, -1, depth + 1);
|
||||
bool res = ScriptAdminMakeJSON(tmp, vm, -1, depth + 1);
|
||||
sq_pop(vm, 2);
|
||||
if (!res) {
|
||||
sq_pop(vm, 1);
|
||||
|
@ -72,7 +85,7 @@
|
|||
std::string key = std::string(buf);
|
||||
|
||||
nlohmann::json value;
|
||||
bool res = MakeJSON(value, vm, -1, depth + 1);
|
||||
bool res = ScriptAdminMakeJSON(value, vm, -1, depth + 1);
|
||||
sq_pop(vm, 2);
|
||||
if (!res) {
|
||||
sq_pop(vm, 1);
|
||||
|
@ -113,7 +126,7 @@
|
|||
}
|
||||
|
||||
nlohmann::json json;
|
||||
if (!ScriptAdmin::MakeJSON(json, vm, -1)) {
|
||||
if (!ScriptAdminMakeJSON(json, vm, -1)) {
|
||||
sq_pushinteger(vm, 0);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#define SCRIPT_ADMIN_HPP
|
||||
|
||||
#include "script_object.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
/**
|
||||
* Class that handles communication with the AdminPort.
|
||||
|
@ -36,17 +35,6 @@ public:
|
|||
*/
|
||||
static bool Send(void *table);
|
||||
#endif /* DOXYGEN_API */
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Convert a Squirrel structure into a JSON object.
|
||||
* @param json The resulting JSON object.
|
||||
* @param vm The VM to operate on.
|
||||
* @param index The index we are currently working for.
|
||||
* @param depth The current depth in the squirrel struct.
|
||||
* @return True iff the conversion was successful.
|
||||
*/
|
||||
static bool MakeJSON(nlohmann::json &data, HSQUIRRELVM vm, SQInteger index, int depth = 0);
|
||||
};
|
||||
|
||||
#endif /* SCRIPT_ADMIN_HPP */
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include "../../engine_cmd.h"
|
||||
#include "table/strings.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
bool ScriptEventEnginePreview::IsEngineValid() const
|
||||
|
@ -127,33 +129,12 @@ ScriptEventAdminPort::ScriptEventAdminPort(const std::string &json) :
|
|||
json(json)
|
||||
{
|
||||
}
|
||||
|
||||
SQInteger ScriptEventAdminPort::GetObject(HSQUIRRELVM vm)
|
||||
{
|
||||
auto json = nlohmann::json::parse(this->json, nullptr, false);
|
||||
|
||||
if (!json.is_object()) {
|
||||
ScriptLog::Error("The root element in the JSON data from AdminPort has to be an object.");
|
||||
|
||||
sq_pushnull(vm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto top = sq_gettop(vm);
|
||||
if (!this->ReadValue(vm, json)) {
|
||||
/* Rewind the stack, removing anything that might be left on top. */
|
||||
sq_settop(vm, top);
|
||||
|
||||
ScriptLog::Error("Received invalid JSON data from AdminPort.");
|
||||
|
||||
sq_pushnull(vm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool ScriptEventAdminPort::ReadValue(HSQUIRRELVM vm, nlohmann::json &json)
|
||||
/**
|
||||
* Convert a JSON part fo Squirrel.
|
||||
* @param vm The VM used.
|
||||
* @param json The JSON part to convert to Squirrel.
|
||||
*/
|
||||
static bool ScriptEventAdminPortReadValue(HSQUIRRELVM vm, nlohmann::json &json)
|
||||
{
|
||||
switch (json.type()) {
|
||||
case nlohmann::json::value_t::null:
|
||||
|
@ -181,7 +162,7 @@ bool ScriptEventAdminPort::ReadValue(HSQUIRRELVM vm, nlohmann::json &json)
|
|||
for (auto &[key, value] : json.items()) {
|
||||
sq_pushstring(vm, key.data(), key.size());
|
||||
|
||||
if (!this->ReadValue(vm, value)) {
|
||||
if (!ScriptEventAdminPortReadValue(vm, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -193,7 +174,7 @@ bool ScriptEventAdminPort::ReadValue(HSQUIRRELVM vm, nlohmann::json &json)
|
|||
sq_newarray(vm, 0);
|
||||
|
||||
for (auto &value : json) {
|
||||
if (!this->ReadValue(vm, value)) {
|
||||
if (!ScriptEventAdminPortReadValue(vm, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -209,3 +190,28 @@ bool ScriptEventAdminPort::ReadValue(HSQUIRRELVM vm, nlohmann::json &json)
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
SQInteger ScriptEventAdminPort::GetObject(HSQUIRRELVM vm)
|
||||
{
|
||||
auto json = nlohmann::json::parse(this->json, nullptr, false);
|
||||
|
||||
if (!json.is_object()) {
|
||||
ScriptLog::Error("The root element in the JSON data from AdminPort has to be an object.");
|
||||
|
||||
sq_pushnull(vm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto top = sq_gettop(vm);
|
||||
if (!ScriptEventAdminPortReadValue(vm, json)) {
|
||||
/* Rewind the stack, removing anything that might be left on top. */
|
||||
sq_settop(vm, top);
|
||||
|
||||
ScriptLog::Error("Received invalid JSON data from AdminPort.");
|
||||
|
||||
sq_pushnull(vm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
#include "script_goal.hpp"
|
||||
#include "script_window.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
/**
|
||||
* Event Vehicle Crash, indicating a vehicle of yours is crashed.
|
||||
* It contains the crash site, the crashed vehicle and the reason for the crash.
|
||||
|
@ -912,13 +910,6 @@ public:
|
|||
|
||||
private:
|
||||
std::string json; ///< The JSON string.
|
||||
|
||||
/**
|
||||
* Convert a JSON part fo Squirrel.
|
||||
* @param vm The VM used.
|
||||
* @param json The JSON part to convert to Squirrel.
|
||||
*/
|
||||
bool ReadValue(HSQUIRRELVM vm, nlohmann::json &json);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "../3rdparty/fmt/format.h"
|
||||
|
||||
#include <squirrel.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
/**
|
||||
* A controller to start enough so we can use Squirrel for testing.
|
||||
|
@ -40,16 +41,13 @@ public:
|
|||
ScriptAllocatorScope scope{&engine};
|
||||
};
|
||||
|
||||
extern bool ScriptAdminMakeJSON(nlohmann::json &json, HSQUIRRELVM vm, SQInteger index, int depth = 0);
|
||||
|
||||
/**
|
||||
* Small wrapper around ScriptAdmin.
|
||||
*
|
||||
* MakeJSON is protected; so for tests, we make a public function with
|
||||
* which we call into the protected one. This prevents accidental use
|
||||
* by the rest of the code, while still being able to test it.
|
||||
* Small wrapper around ScriptAdmin's MakeJSON that prepares the Squirrel
|
||||
* engine if it was called from actual scripting..
|
||||
*/
|
||||
class TestScriptAdmin : public ScriptAdmin {
|
||||
public:
|
||||
static std::optional<std::string> MakeJSON(std::string_view squirrel)
|
||||
static std::optional<std::string> TestScriptAdminMakeJSON(std::string_view squirrel)
|
||||
{
|
||||
auto vm = sq_open(1024);
|
||||
/* sq_compile creates a closure with our snipper, which is a table.
|
||||
|
@ -73,7 +71,7 @@ public:
|
|||
|
||||
/* Feed the snippet into the MakeJSON function. */
|
||||
nlohmann::json json;
|
||||
if (!ScriptAdmin::MakeJSON(json, vm, -1)) {
|
||||
if (!ScriptAdminMakeJSON(json, vm, -1)) {
|
||||
sq_close(vm);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -112,74 +110,72 @@ public:
|
|||
REQUIRE(sq_gettype(vm, -1) == OT_TABLE);
|
||||
|
||||
nlohmann::json squirrel_json;
|
||||
REQUIRE(ScriptAdmin::MakeJSON(squirrel_json, vm, -1) == true);
|
||||
REQUIRE(ScriptAdminMakeJSON(squirrel_json, vm, -1) == true);
|
||||
|
||||
sq_close(vm);
|
||||
return squirrel_json.dump();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
TEST_CASE("Squirrel -> JSON conversion")
|
||||
{
|
||||
TestScriptController controller;
|
||||
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = null })sq") == R"json({"test":null})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = 1 })sq") == R"json({"test":1})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = -1 })sq") == R"json({"test":-1})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = true })sq") == R"json({"test":true})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = "a" })sq") == R"json({"test":"a"})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = [ ] })sq") == R"json({"test":[]})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = [ 1 ] })sq") == R"json({"test":[1]})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = [ 1, "a", true, { test = 1 }, [], null ] })sq") == R"json({"test":[1,"a",true,{"test":1},[],null]})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { } })sq") == R"json({"test":{}})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { test = 1 } })sq") == R"json({"test":{"test":1}})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { test = 1, test = 2 } })sq") == R"json({"test":{"test":2}})json");
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { test = 1, test2 = [ 2 ] } })sq") == R"json({"test":{"test":1,"test2":[2]}})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = null })sq") == R"json({"test":null})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = 1 })sq") == R"json({"test":1})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = -1 })sq") == R"json({"test":-1})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = true })sq") == R"json({"test":true})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = "a" })sq") == R"json({"test":"a"})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = [ ] })sq") == R"json({"test":[]})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = [ 1 ] })sq") == R"json({"test":[1]})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = [ 1, "a", true, { test = 1 }, [], null ] })sq") == R"json({"test":[1,"a",true,{"test":1},[],null]})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = { } })sq") == R"json({"test":{}})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = { test = 1 } })sq") == R"json({"test":{"test":1}})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = { test = 1, test = 2 } })sq") == R"json({"test":{"test":2}})json");
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = { test = 1, test2 = [ 2 ] } })sq") == R"json({"test":{"test":1,"test2":[2]}})json");
|
||||
|
||||
/* Cases that should fail, as we cannot convert a class to JSON. */
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = DummyClass })sq") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = [ 1, DummyClass ] })sq") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { test = 1, test2 = DummyClass } })sq") == std::nullopt);
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = DummyClass })sq") == std::nullopt);
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = [ 1, DummyClass ] })sq") == std::nullopt);
|
||||
CHECK(TestScriptAdminMakeJSON(R"sq({ test = { test = 1, test2 = DummyClass } })sq") == std::nullopt);
|
||||
}
|
||||
|
||||
TEST_CASE("JSON -> Squirrel conversion")
|
||||
{
|
||||
TestScriptController controller;
|
||||
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": null })json") == R"json({"test":null})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": 1 })json") == R"json({"test":1})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": -1 })json") == R"json({"test":-1})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": true })json") == R"json({"test":true})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": "a" })json") == R"json({"test":"a"})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [] })json") == R"json({"test":[]})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [ 1 ] })json") == R"json({"test":[1]})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [ 1, "a", true, { "test": 1 }, [], null ] })json") == R"json({"test":[1,"a",true,{"test":1},[],null]})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": {} })json") == R"json({"test":{}})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": { "test": 1 } })json") == R"json({"test":{"test":1}})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": { "test": 2 } })json") == R"json({"test":{"test":2}})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": { "test": 1, "test2": [ 2 ] } })json") == R"json({"test":{"test":1,"test2":[2]}})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": null })json") == R"json({"test":null})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": 1 })json") == R"json({"test":1})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": -1 })json") == R"json({"test":-1})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": true })json") == R"json({"test":true})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": "a" })json") == R"json({"test":"a"})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": [] })json") == R"json({"test":[]})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": [ 1 ] })json") == R"json({"test":[1]})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": [ 1, "a", true, { "test": 1 }, [], null ] })json") == R"json({"test":[1,"a",true,{"test":1},[],null]})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": {} })json") == R"json({"test":{}})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": { "test": 1 } })json") == R"json({"test":{"test":1}})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": { "test": 2 } })json") == R"json({"test":{"test":2}})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": { "test": 1, "test2": [ 2 ] } })json") == R"json({"test":{"test":1,"test2":[2]}})json");
|
||||
|
||||
/* Check if spaces are properly ignored. */
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({"test":1})json") == R"json({"test":1})json");
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({"test": 1})json") == R"json({"test":1})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({"test":1})json") == R"json({"test":1})json");
|
||||
CHECK(TestScriptEventAdminPort(R"json({"test": 1})json") == R"json({"test":1})json");
|
||||
|
||||
/* Valid JSON but invalid Squirrel (read: floats). */
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": 1.1 })json") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [ 1, 3, 1.1 ] })json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": 1.1 })json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": [ 1, 3, 1.1 ] })json") == std::nullopt);
|
||||
|
||||
/* Root element has to be an object. */
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json( 1 )json") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json( "a" )json") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json( [ 1 ] )json") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json( null )json") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json( true )json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json( 1 )json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json( "a" )json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json( [ 1 ] )json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json( null )json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json( true )json") == std::nullopt);
|
||||
|
||||
/* Cases that should fail, as it is invalid JSON. */
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({"test":test})json") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": 1 )json") == std::nullopt); // Missing closing }
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json( "test": 1})json") == std::nullopt); // Missing opening {
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test" = 1})json") == std::nullopt);
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [ 1 })json") == std::nullopt); // Missing closing ]
|
||||
CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": 1 ] })json") == std::nullopt); // Missing opening [
|
||||
CHECK(TestScriptEventAdminPort(R"json({"test":test})json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": 1 )json") == std::nullopt); // Missing closing }
|
||||
CHECK(TestScriptEventAdminPort(R"json( "test": 1})json") == std::nullopt); // Missing opening {
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test" = 1})json") == std::nullopt);
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": [ 1 })json") == std::nullopt); // Missing closing ]
|
||||
CHECK(TestScriptEventAdminPort(R"json({ "test": 1 ] })json") == std::nullopt); // Missing opening [
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue