mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 14:35:52 +00:00
Merge branch 'develop' into ripple/wamr
This commit is contained in:
@@ -202,6 +202,175 @@ non_object_in_array(std::string const& item, Json::UInt index)
|
|||||||
" is not an object. Arrays may only contain objects.");
|
" is not an object. Arrays may only contain objects.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class STResult, class Integer>
|
||||||
|
static std::optional<detail::STVar>
|
||||||
|
parseUnsigned(
|
||||||
|
SField const& field,
|
||||||
|
std::string const& json_name,
|
||||||
|
std::string const& fieldName,
|
||||||
|
SField const* name,
|
||||||
|
Json::Value const& value,
|
||||||
|
Json::Value& error)
|
||||||
|
{
|
||||||
|
std::optional<detail::STVar> ret;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (value.isString())
|
||||||
|
{
|
||||||
|
ret = detail::make_stvar<STResult>(
|
||||||
|
field,
|
||||||
|
safe_cast<typename STResult::value_type>(
|
||||||
|
beast::lexicalCastThrow<Integer>(value.asString())));
|
||||||
|
}
|
||||||
|
else if (value.isInt())
|
||||||
|
{
|
||||||
|
ret = detail::make_stvar<STResult>(
|
||||||
|
field,
|
||||||
|
to_unsigned<typename STResult::value_type>(value.asInt()));
|
||||||
|
}
|
||||||
|
else if (value.isUInt())
|
||||||
|
{
|
||||||
|
ret = detail::make_stvar<STResult>(
|
||||||
|
field,
|
||||||
|
to_unsigned<typename STResult::value_type>(value.asUInt()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error = bad_type(json_name, fieldName);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception const&)
|
||||||
|
{
|
||||||
|
error = invalid_data(json_name, fieldName);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class STResult, class Integer = std::uint16_t>
|
||||||
|
static std::optional<detail::STVar>
|
||||||
|
parseUint16(
|
||||||
|
SField const& field,
|
||||||
|
std::string const& json_name,
|
||||||
|
std::string const& fieldName,
|
||||||
|
SField const* name,
|
||||||
|
Json::Value const& value,
|
||||||
|
Json::Value& error)
|
||||||
|
{
|
||||||
|
std::optional<detail::STVar> ret;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (value.isString())
|
||||||
|
{
|
||||||
|
std::string const strValue = value.asString();
|
||||||
|
|
||||||
|
if (!strValue.empty() &&
|
||||||
|
((strValue[0] < '0') || (strValue[0] > '9')))
|
||||||
|
{
|
||||||
|
if (field == sfTransactionType)
|
||||||
|
{
|
||||||
|
ret = detail::make_stvar<STResult>(
|
||||||
|
field,
|
||||||
|
safe_cast<typename STResult::value_type>(
|
||||||
|
static_cast<Integer>(
|
||||||
|
TxFormats::getInstance().findTypeByName(
|
||||||
|
strValue))));
|
||||||
|
|
||||||
|
if (*name == sfGeneric)
|
||||||
|
name = &sfTransaction;
|
||||||
|
}
|
||||||
|
else if (field == sfLedgerEntryType)
|
||||||
|
{
|
||||||
|
ret = detail::make_stvar<STResult>(
|
||||||
|
field,
|
||||||
|
safe_cast<typename STResult::value_type>(
|
||||||
|
static_cast<Integer>(
|
||||||
|
LedgerFormats::getInstance().findTypeByName(
|
||||||
|
strValue))));
|
||||||
|
|
||||||
|
if (*name == sfGeneric)
|
||||||
|
name = &sfLedgerEntry;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error = invalid_data(json_name, fieldName);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ret)
|
||||||
|
return parseUnsigned<STResult, Integer>(
|
||||||
|
field, json_name, fieldName, name, value, error);
|
||||||
|
}
|
||||||
|
catch (std::exception const&)
|
||||||
|
{
|
||||||
|
error = invalid_data(json_name, fieldName);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class STResult, class Integer = std::uint32_t>
|
||||||
|
static std::optional<detail::STVar>
|
||||||
|
parseUint32(
|
||||||
|
SField const& field,
|
||||||
|
std::string const& json_name,
|
||||||
|
std::string const& fieldName,
|
||||||
|
SField const* name,
|
||||||
|
Json::Value const& value,
|
||||||
|
Json::Value& error)
|
||||||
|
{
|
||||||
|
std::optional<detail::STVar> ret;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (value.isString())
|
||||||
|
{
|
||||||
|
if (field == sfPermissionValue)
|
||||||
|
{
|
||||||
|
std::string const strValue = value.asString();
|
||||||
|
auto const granularPermission =
|
||||||
|
Permission::getInstance().getGranularValue(strValue);
|
||||||
|
if (granularPermission)
|
||||||
|
{
|
||||||
|
ret = detail::make_stvar<STResult>(
|
||||||
|
field, *granularPermission);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto const& txType =
|
||||||
|
TxFormats::getInstance().findTypeByName(strValue);
|
||||||
|
ret = detail::make_stvar<STResult>(
|
||||||
|
field,
|
||||||
|
Permission::getInstance().txToPermissionType(txType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret = detail::make_stvar<STResult>(
|
||||||
|
field,
|
||||||
|
safe_cast<typename STResult::value_type>(
|
||||||
|
beast::lexicalCastThrow<Integer>(value.asString())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ret)
|
||||||
|
return parseUnsigned<STResult, Integer>(
|
||||||
|
field, json_name, fieldName, name, value, error);
|
||||||
|
}
|
||||||
|
catch (std::exception const&)
|
||||||
|
{
|
||||||
|
error = invalid_data(json_name, fieldName);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// This function is used by parseObject to parse any JSON type that doesn't
|
// This function is used by parseObject to parse any JSON type that doesn't
|
||||||
// recurse. Everything represented here is a leaf-type.
|
// recurse. Everything represented here is a leaf-type.
|
||||||
static std::optional<detail::STVar>
|
static std::optional<detail::STVar>
|
||||||
@@ -302,130 +471,18 @@ parseLeaf(
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case STI_UINT16:
|
case STI_UINT16:
|
||||||
try
|
ret = parseUint16<STUInt16>(
|
||||||
{
|
field, json_name, fieldName, name, value, error);
|
||||||
if (value.isString())
|
if (!ret)
|
||||||
{
|
|
||||||
std::string const strValue = value.asString();
|
|
||||||
|
|
||||||
if (!strValue.empty() &&
|
|
||||||
((strValue[0] < '0') || (strValue[0] > '9')))
|
|
||||||
{
|
|
||||||
if (field == sfTransactionType)
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt16>(
|
|
||||||
field,
|
|
||||||
static_cast<std::uint16_t>(
|
|
||||||
TxFormats::getInstance().findTypeByName(
|
|
||||||
strValue)));
|
|
||||||
|
|
||||||
if (*name == sfGeneric)
|
|
||||||
name = &sfTransaction;
|
|
||||||
}
|
|
||||||
else if (field == sfLedgerEntryType)
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt16>(
|
|
||||||
field,
|
|
||||||
static_cast<std::uint16_t>(
|
|
||||||
LedgerFormats::getInstance().findTypeByName(
|
|
||||||
strValue)));
|
|
||||||
|
|
||||||
if (*name == sfGeneric)
|
|
||||||
name = &sfLedgerEntry;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
error = invalid_data(json_name, fieldName);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt16>(
|
|
||||||
field,
|
|
||||||
beast::lexicalCastThrow<std::uint16_t>(strValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value.isInt())
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt16>(
|
|
||||||
field, to_unsigned<std::uint16_t>(value.asInt()));
|
|
||||||
}
|
|
||||||
else if (value.isUInt())
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt16>(
|
|
||||||
field, to_unsigned<std::uint16_t>(value.asUInt()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
error = bad_type(json_name, fieldName);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (std::exception const&)
|
|
||||||
{
|
|
||||||
error = invalid_data(json_name, fieldName);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STI_UINT32:
|
case STI_UINT32:
|
||||||
try
|
ret = parseUint32<STUInt32>(
|
||||||
{
|
field, json_name, fieldName, name, value, error);
|
||||||
if (value.isString())
|
if (!ret)
|
||||||
{
|
|
||||||
if (field == sfPermissionValue)
|
|
||||||
{
|
|
||||||
std::string const strValue = value.asString();
|
|
||||||
auto const granularPermission =
|
|
||||||
Permission::getInstance().getGranularValue(
|
|
||||||
strValue);
|
|
||||||
if (granularPermission)
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt32>(
|
|
||||||
field, *granularPermission);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto const& txType =
|
|
||||||
TxFormats::getInstance().findTypeByName(
|
|
||||||
strValue);
|
|
||||||
ret = detail::make_stvar<STUInt32>(
|
|
||||||
field,
|
|
||||||
Permission::getInstance().txToPermissionType(
|
|
||||||
txType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt32>(
|
|
||||||
field,
|
|
||||||
beast::lexicalCastThrow<std::uint32_t>(
|
|
||||||
value.asString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value.isInt())
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt32>(
|
|
||||||
field, to_unsigned<std::uint32_t>(value.asInt()));
|
|
||||||
}
|
|
||||||
else if (value.isUInt())
|
|
||||||
{
|
|
||||||
ret = detail::make_stvar<STUInt32>(
|
|
||||||
field, safe_cast<std::uint32_t>(value.asUInt()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
error = bad_type(json_name, fieldName);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (std::exception const&)
|
|
||||||
{
|
|
||||||
error = invalid_data(json_name, fieldName);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -19,223 +19,11 @@
|
|||||||
|
|
||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
|
|
||||||
#include <xrpl/beast/unit_test.h>
|
|
||||||
#include <xrpl/json/json_reader.h>
|
|
||||||
#include <xrpl/json/to_string.h>
|
|
||||||
#include <xrpl/protocol/SecretKey.h>
|
|
||||||
#include <xrpl/protocol/jss.h>
|
|
||||||
#include <xrpl/protocol/st.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class STObject_test : public beast::unit_test::suite
|
class STObject_test : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool
|
|
||||||
parseJSONString(std::string const& json, Json::Value& to)
|
|
||||||
{
|
|
||||||
Json::Reader reader;
|
|
||||||
return reader.parse(json, to) && to.isObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testParseJSONArrayWithInvalidChildrenObjects()
|
|
||||||
{
|
|
||||||
testcase("parse json array invalid children");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
|
|
||||||
STArray/STObject constructs don't really map perfectly to json
|
|
||||||
arrays/objects.
|
|
||||||
|
|
||||||
STObject is an associative container, mapping fields to value, but
|
|
||||||
an STObject may also have a Field as its name, stored outside the
|
|
||||||
associative structure. The name is important, so to maintain
|
|
||||||
fidelity, it will take TWO json objects to represent them.
|
|
||||||
|
|
||||||
*/
|
|
||||||
std::string faulty(
|
|
||||||
"{\"Template\":[{"
|
|
||||||
"\"ModifiedNode\":{\"Sequence\":1}, "
|
|
||||||
"\"DeletedNode\":{\"Sequence\":1}"
|
|
||||||
"}]}");
|
|
||||||
|
|
||||||
std::unique_ptr<STObject> so;
|
|
||||||
Json::Value faultyJson;
|
|
||||||
bool parsedOK(parseJSONString(faulty, faultyJson));
|
|
||||||
unexpected(!parsedOK, "failed to parse");
|
|
||||||
STParsedJSONObject parsed("test", faultyJson);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
}
|
|
||||||
catch (std::runtime_error& e)
|
|
||||||
{
|
|
||||||
std::string what(e.what());
|
|
||||||
unexpected(what.find("First level children of `Template`") != 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testParseJSONArray()
|
|
||||||
{
|
|
||||||
testcase("parse json array");
|
|
||||||
std::string const json(
|
|
||||||
"{\"Template\":[{\"ModifiedNode\":{\"Sequence\":1}}]}");
|
|
||||||
|
|
||||||
Json::Value jsonObject;
|
|
||||||
bool parsedOK(parseJSONString(json, jsonObject));
|
|
||||||
if (parsedOK)
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jsonObject);
|
|
||||||
BEAST_EXPECT(parsed.object);
|
|
||||||
std::string const& serialized(
|
|
||||||
to_string(parsed.object->getJson(JsonOptions::none)));
|
|
||||||
BEAST_EXPECT(serialized == json);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fail("Couldn't parse json: " + json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testParseJSONEdgeCases()
|
|
||||||
{
|
|
||||||
testcase("parse json object");
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const goodJson(R"({"CloseResolution":19,"Method":250,)"
|
|
||||||
R"("TransactionResult":"tecFROZEN"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
if (BEAST_EXPECT(parsed.object))
|
|
||||||
{
|
|
||||||
std::string const& serialized(
|
|
||||||
to_string(parsed.object->getJson(JsonOptions::none)));
|
|
||||||
BEAST_EXPECT(serialized == goodJson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const goodJson(
|
|
||||||
R"({"CloseResolution":19,"Method":"250",)"
|
|
||||||
R"("TransactionResult":"tecFROZEN"})");
|
|
||||||
std::string const expectedJson(
|
|
||||||
R"({"CloseResolution":19,"Method":250,)"
|
|
||||||
R"("TransactionResult":"tecFROZEN"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
|
||||||
{
|
|
||||||
// Integer values are always parsed as int,
|
|
||||||
// unless they're too big. We want a small uint.
|
|
||||||
jv["CloseResolution"] = Json::UInt(19);
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
if (BEAST_EXPECT(parsed.object))
|
|
||||||
{
|
|
||||||
std::string const& serialized(
|
|
||||||
to_string(parsed.object->getJson(JsonOptions::none)));
|
|
||||||
BEAST_EXPECT(serialized == expectedJson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
|
||||||
R"("TransactionResult":"terQUEUED"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.TransactionResult' is out of range.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(R"({"CloseResolution":19,"Method":"pony",)"
|
|
||||||
R"("TransactionResult":"tesSUCCESS"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.Method' has bad type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(
|
|
||||||
R"({"CloseResolution":19,"Method":3294967296,)"
|
|
||||||
R"("TransactionResult":"tesSUCCESS"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.Method' is out of range.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(R"({"CloseResolution":-10,"Method":42,)"
|
|
||||||
R"("TransactionResult":"tesSUCCESS"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.CloseResolution' is out of range.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::string const json(
|
|
||||||
R"({"CloseResolution":19,"Method":3.141592653,)"
|
|
||||||
R"("TransactionResult":"tesSUCCESS"})");
|
|
||||||
|
|
||||||
Json::Value jv;
|
|
||||||
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
|
||||||
{
|
|
||||||
STParsedJSONObject parsed("test", jv);
|
|
||||||
BEAST_EXPECT(!parsed.object);
|
|
||||||
BEAST_EXPECT(parsed.error);
|
|
||||||
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
|
||||||
BEAST_EXPECT(
|
|
||||||
parsed.error[jss::error_message] ==
|
|
||||||
"Field 'test.Method' has bad type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
testSerialization()
|
testSerialization()
|
||||||
{
|
{
|
||||||
@@ -730,9 +518,6 @@ public:
|
|||||||
|
|
||||||
testFields();
|
testFields();
|
||||||
testSerialization();
|
testSerialization();
|
||||||
testParseJSONArray();
|
|
||||||
testParseJSONArrayWithInvalidChildrenObjects();
|
|
||||||
testParseJSONEdgeCases();
|
|
||||||
testMalformed();
|
testMalformed();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
339
src/test/protocol/STParsedJSON_test.cpp
Normal file
339
src/test/protocol/STParsedJSON_test.cpp
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <test/jtx.h>
|
||||||
|
|
||||||
|
#include <xrpl/json/json_reader.h>
|
||||||
|
#include <xrpl/protocol/st.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
class STParsedJSON_test : public beast::unit_test::suite
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool
|
||||||
|
parseJSONString(std::string const& json, Json::Value& to)
|
||||||
|
{
|
||||||
|
Json::Reader reader;
|
||||||
|
return reader.parse(json, to) && to.isObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testParseJSONArrayWithInvalidChildrenObjects()
|
||||||
|
{
|
||||||
|
testcase("parse json array invalid children");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|
||||||
|
STArray/STObject constructs don't really map perfectly to json
|
||||||
|
arrays/objects.
|
||||||
|
|
||||||
|
STObject is an associative container, mapping fields to value, but
|
||||||
|
an STObject may also have a Field as its name, stored outside the
|
||||||
|
associative structure. The name is important, so to maintain
|
||||||
|
fidelity, it will take TWO json objects to represent them.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::string faulty(
|
||||||
|
"{\"Template\":[{"
|
||||||
|
"\"ModifiedNode\":{\"Sequence\":1}, "
|
||||||
|
"\"DeletedNode\":{\"Sequence\":1}"
|
||||||
|
"}]}");
|
||||||
|
|
||||||
|
std::unique_ptr<STObject> so;
|
||||||
|
Json::Value faultyJson;
|
||||||
|
bool parsedOK(parseJSONString(faulty, faultyJson));
|
||||||
|
unexpected(!parsedOK, "failed to parse");
|
||||||
|
STParsedJSONObject parsed("test", faultyJson);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
}
|
||||||
|
catch (std::runtime_error& e)
|
||||||
|
{
|
||||||
|
std::string what(e.what());
|
||||||
|
unexpected(what.find("First level children of `Template`") != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testParseJSONArray()
|
||||||
|
{
|
||||||
|
testcase("parse json array");
|
||||||
|
std::string const json(
|
||||||
|
"{\"Template\":[{\"ModifiedNode\":{\"Sequence\":1}}]}");
|
||||||
|
|
||||||
|
Json::Value jsonObject;
|
||||||
|
bool parsedOK(parseJSONString(json, jsonObject));
|
||||||
|
if (parsedOK)
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jsonObject);
|
||||||
|
BEAST_EXPECT(parsed.object);
|
||||||
|
std::string const& serialized(
|
||||||
|
to_string(parsed.object->getJson(JsonOptions::none)));
|
||||||
|
BEAST_EXPECT(serialized == json);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fail("Couldn't parse json: " + json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testParseJSONEdgeCases()
|
||||||
|
{
|
||||||
|
testcase("parse json object");
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const goodJson(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransactionResult":"tecFROZEN"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
if (BEAST_EXPECT(parsed.object))
|
||||||
|
{
|
||||||
|
std::string const& serialized(
|
||||||
|
to_string(parsed.object->getJson(JsonOptions::none)));
|
||||||
|
BEAST_EXPECT(serialized == goodJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const goodJson(
|
||||||
|
R"({"CloseResolution":19,"Method":"250",)"
|
||||||
|
R"("TransactionResult":"tecFROZEN"})");
|
||||||
|
std::string const expectedJson(
|
||||||
|
R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransactionResult":"tecFROZEN"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
||||||
|
{
|
||||||
|
// Integer values are always parsed as int,
|
||||||
|
// unless they're too big. We want a small uint.
|
||||||
|
jv["CloseResolution"] = Json::UInt(19);
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
if (BEAST_EXPECT(parsed.object))
|
||||||
|
{
|
||||||
|
std::string const& serialized(
|
||||||
|
to_string(parsed.object->getJson(JsonOptions::none)));
|
||||||
|
BEAST_EXPECT(serialized == expectedJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const goodJson(
|
||||||
|
R"({"CloseResolution":"19","Method":"250",)"
|
||||||
|
R"("TransactionResult":"tecFROZEN"})");
|
||||||
|
std::string const expectedJson(
|
||||||
|
R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransactionResult":"tecFROZEN"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
||||||
|
{
|
||||||
|
// Integer values are always parsed as int,
|
||||||
|
// unless they're too big. We want a small uint.
|
||||||
|
jv["CloseResolution"] = Json::UInt(19);
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
if (BEAST_EXPECT(parsed.object))
|
||||||
|
{
|
||||||
|
std::string const& serialized(
|
||||||
|
to_string(parsed.object->getJson(JsonOptions::none)));
|
||||||
|
BEAST_EXPECT(serialized == expectedJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransactionResult":"terQUEUED"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.TransactionResult' is out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":"pony",)"
|
||||||
|
R"("TransactionResult":"tesSUCCESS"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.Method' has bad type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(
|
||||||
|
R"({"CloseResolution":19,"Method":3294967296,)"
|
||||||
|
R"("TransactionResult":"tesSUCCESS"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.Method' is out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":-10,"Method":42,)"
|
||||||
|
R"("TransactionResult":"tesSUCCESS"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.CloseResolution' is out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(
|
||||||
|
R"({"CloseResolution":19,"Method":3.141592653,)"
|
||||||
|
R"("TransactionResult":"tesSUCCESS"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.Method' has bad type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const goodJson(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransferFee":"65535"})");
|
||||||
|
std::string const expectedJson(
|
||||||
|
R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransferFee":65535})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(goodJson, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
if (BEAST_EXPECT(parsed.object))
|
||||||
|
{
|
||||||
|
std::string const& serialized(
|
||||||
|
to_string(parsed.object->getJson(JsonOptions::none)));
|
||||||
|
BEAST_EXPECT(serialized == expectedJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransferFee":"65536"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.TransferFee' has invalid data.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransferFee":"Payment"})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.TransferFee' has invalid data.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const json(R"({"CloseResolution":19,"Method":250,)"
|
||||||
|
R"("TransferFee":true})");
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
if (BEAST_EXPECT(parseJSONString(json, jv)))
|
||||||
|
{
|
||||||
|
STParsedJSONObject parsed("test", jv);
|
||||||
|
BEAST_EXPECT(!parsed.object);
|
||||||
|
BEAST_EXPECT(parsed.error);
|
||||||
|
BEAST_EXPECT(parsed.error[jss::error] == "invalidParams");
|
||||||
|
BEAST_EXPECT(
|
||||||
|
parsed.error[jss::error_message] ==
|
||||||
|
"Field 'test.TransferFee' has bad type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
run() override
|
||||||
|
{
|
||||||
|
// Instantiate a jtx::Env so debugLog writes are exercised.
|
||||||
|
test::jtx::Env env(*this);
|
||||||
|
testParseJSONArrayWithInvalidChildrenObjects();
|
||||||
|
testParseJSONArray();
|
||||||
|
testParseJSONEdgeCases();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BEAST_DEFINE_TESTSUITE(STParsedJSON, protocol, ripple);
|
||||||
|
|
||||||
|
} // namespace ripple
|
||||||
Reference in New Issue
Block a user