mirror of
https://github.com/tristanpenman/valijson.git
synced 2024-12-12 18:20:27 +01:00
a30ef97465
This patch adds support for a new Cmake option, VALIJSON_USE_EXCEPTIONS. If specified and set to `0`, Valijson will disable all exception throwing, add the `-fno-exceptions` compiler flag, and print to std::err and abort where exceptions would have been thrown. NOTE: to set the value of a CMake option, the easiest way is to modify the appropriate source line in the <build folder>/CMakeCache.txt file. Bug: #106
310 lines
12 KiB
C++
310 lines
12 KiB
C++
#include <memory>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <valijson/internal/json_pointer.hpp>
|
|
|
|
#include <valijson/adapters/rapidjson_adapter.hpp>
|
|
|
|
using valijson::adapters::RapidJsonAdapter;
|
|
using valijson::internal::json_pointer::resolveJsonPointer;
|
|
|
|
typedef rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> RapidJsonCrtAllocator;
|
|
|
|
class TestJsonPointer : public testing::Test
|
|
{
|
|
|
|
};
|
|
|
|
struct JsonPointerTestCase
|
|
{
|
|
/// Description of test case
|
|
std::string description;
|
|
|
|
/// Document to traverse when resolving the JSON Pointer
|
|
rapidjson::Value value;
|
|
|
|
/// JSON Pointer that should guide traversal of the document
|
|
std::string jsonPointer;
|
|
|
|
/// Optional reference to the expected result from the original document
|
|
rapidjson::Value *expectedValue;
|
|
};
|
|
|
|
std::vector<std::shared_ptr<JsonPointerTestCase> >
|
|
testCasesForSingleLevelObjectPointers(
|
|
RapidJsonCrtAllocator &allocator)
|
|
{
|
|
typedef std::shared_ptr<JsonPointerTestCase> TestCase;
|
|
|
|
std::vector<TestCase> testCases;
|
|
|
|
TestCase testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving '#' should cause an exception to be thrown";
|
|
testCase->value.SetNull();
|
|
testCase->jsonPointer = "#";
|
|
testCase->expectedValue = nullptr;
|
|
testCases.push_back(testCase);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving an empty string should return the root node";
|
|
testCase->value.SetNull();
|
|
testCase->jsonPointer = "";
|
|
testCase->expectedValue = &testCase->value;
|
|
testCases.push_back(testCase);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving '/' should return the root node";
|
|
testCase->value.SetNull();
|
|
testCase->jsonPointer = "/";
|
|
testCase->expectedValue = &testCase->value;
|
|
testCases.push_back(testCase);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving '//' should return the root node";
|
|
testCase->value.SetNull();
|
|
testCase->jsonPointer = "//";
|
|
testCase->expectedValue = &testCase->value;
|
|
testCases.push_back(testCase);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '/test' in object containing one member named 'test'";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("test", "test", allocator);
|
|
testCase->jsonPointer = "/test";
|
|
testCase->expectedValue = &testCase->value.FindMember("test")->value;
|
|
testCases.push_back(testCase);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '/test/' in object containing one member named 'test'";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("test", "test", allocator);
|
|
testCase->jsonPointer = "/test/";
|
|
testCase->expectedValue = &testCase->value.FindMember("test")->value;
|
|
testCases.push_back(testCase);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '//test//' in object containing one member named 'test'";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("test", "test", allocator);
|
|
testCase->jsonPointer = "//test//";
|
|
testCase->expectedValue = &testCase->value.FindMember("test")->value;
|
|
testCases.push_back(testCase);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '/missing' in object containing one member name 'test'";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("test", "test", allocator);
|
|
testCase->jsonPointer = "/missing";
|
|
testCase->expectedValue = nullptr;
|
|
testCases.push_back(testCase);
|
|
|
|
{
|
|
rapidjson::Value nonemptyString;
|
|
nonemptyString.SetString("hello, world");
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '/value/foo' fails because 'value' is not an object (but a non empty string)";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("value", nonemptyString, allocator);
|
|
testCase->jsonPointer = "/value/bar";
|
|
testCase->expectedValue = &testCase->value;
|
|
testCase->expectedValue = nullptr;
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
{
|
|
rapidjson::Value emptyString;
|
|
emptyString.SetString("");
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '/empty/after_empty' fails because 'empty' is an empty string";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("empty", emptyString, allocator);
|
|
testCase->jsonPointer = "/empty/after_empty";
|
|
testCase->expectedValue = nullptr;
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
{
|
|
rapidjson::Value testArray;
|
|
testArray.SetArray();
|
|
testArray.PushBack("test0", allocator);
|
|
testArray.PushBack("test1", allocator);
|
|
testArray.PushBack("test2", allocator);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '/test/0' in object containing one member containing an array with 3 elements";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("test", testArray, allocator);
|
|
testCase->jsonPointer = "/test/0";
|
|
testCase->expectedValue = &testCase->value.FindMember("test")->value[rapidjson::SizeType(0)];
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
{
|
|
rapidjson::Value testArray;
|
|
testArray.SetArray();
|
|
testArray.PushBack("test0", allocator);
|
|
testArray.PushBack("test1", allocator);
|
|
testArray.PushBack("test2", allocator);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '/test/1' in object containing one member containing an array with 3 elements";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("test", testArray, allocator);
|
|
testCase->jsonPointer = "/test/1";
|
|
testCase->expectedValue = &testCase->value.FindMember("test")->value[rapidjson::SizeType(1)];
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
{
|
|
rapidjson::Value testArray;
|
|
testArray.SetArray();
|
|
testArray.PushBack("test0", allocator);
|
|
testArray.PushBack("test1", allocator);
|
|
testArray.PushBack("test2", allocator);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolve '/test/2' in object containing one member containing an array with 3 elements";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("test", testArray, allocator);
|
|
testCase->jsonPointer = "/test/2";
|
|
testCase->expectedValue = &testCase->value.FindMember("test")->value[rapidjson::SizeType(2)];
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
{
|
|
rapidjson::Value testArray;
|
|
testArray.SetArray();
|
|
testArray.PushBack("test0", allocator);
|
|
testArray.PushBack("test1", allocator);
|
|
testArray.PushBack("test2", allocator);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving '/test/3' in object containing one member containing "
|
|
"an array with 3 elements should throw an exception";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("test", testArray, allocator);
|
|
testCase->jsonPointer = "/test/3";
|
|
testCase->expectedValue = nullptr;
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
//
|
|
// Allow the "-" character is not useful within the context of this library,
|
|
// there is an explicit check for it, so that a custom error message can
|
|
// be included in the exception that is thrown.
|
|
//
|
|
// From the JSON Pointer specification (RFC 6901, April 2013):
|
|
//
|
|
// Note that the use of the "-" character to index an array will always
|
|
// result in such an error condition because by definition it refers to
|
|
// a nonexistent array element. Thus, applications of JSON Pointer need
|
|
// to specify how that character is to be handled, if it is to be
|
|
// useful.
|
|
//
|
|
|
|
{
|
|
rapidjson::Value testArray;
|
|
testArray.SetArray();
|
|
testArray.PushBack("test0", allocator);
|
|
testArray.PushBack("test1", allocator);
|
|
testArray.PushBack("test2", allocator);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving '/test/-' in object containing one member containing "
|
|
"an array with 3 elements should throw an exception";
|
|
testCase->value.SetNull();
|
|
testCase->jsonPointer = "/test/-";
|
|
testCase->expectedValue = nullptr;
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
//
|
|
// The following tests ensure that escape sequences are handled correctly.
|
|
//
|
|
// From the JSON Pointer specification (RFC 6901, April 2013):
|
|
//
|
|
// Evaluation of each reference token begins by decoding any escaped
|
|
// character sequence. This is performed by first transforming any
|
|
// occurrence of the sequence '~1' to '/', and then transforming any
|
|
// occurrence of the sequence '~0' to '~'. By performing the
|
|
// substitutions in this order, an implementation avoids the error of
|
|
// turning '~01' first into '~1' and then into '/', which would be
|
|
// incorrect (the string '~01' correctly becomes '~1' after
|
|
// transformation).
|
|
//
|
|
|
|
{
|
|
rapidjson::Value value;
|
|
value.SetDouble(10.);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving '/hello~1world' in object containing one member named "
|
|
"'hello/world' should return the associated value";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("hello/world", value, allocator);
|
|
testCase->jsonPointer = "/hello~1world";
|
|
testCase->expectedValue = &testCase->value.FindMember("hello/world")->value;
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
{
|
|
rapidjson::Value value;
|
|
value.SetDouble(10.);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving '/hello~0world' in object containing one member named "
|
|
"'hello~world' should return the associated value";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("hello~world", value, allocator);
|
|
testCase->jsonPointer = "/hello~0world";
|
|
testCase->expectedValue = &testCase->value.FindMember("hello~world")->value;
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
{
|
|
rapidjson::Value value;
|
|
value.SetDouble(10.);
|
|
|
|
testCase = std::make_shared<JsonPointerTestCase>();
|
|
testCase->description = "Resolving '/hello~01world' in object containing one member named "
|
|
"'hello~1world' should return the associated value";
|
|
testCase->value.SetObject();
|
|
testCase->value.AddMember("hello~1world", value, allocator);
|
|
testCase->jsonPointer = "/hello~01world";
|
|
testCase->expectedValue = &testCase->value.FindMember("hello~1world")->value;
|
|
testCases.push_back(testCase);
|
|
}
|
|
|
|
return testCases;
|
|
}
|
|
|
|
TEST_F(TestJsonPointer, JsonPointerTestCases)
|
|
{
|
|
typedef std::vector<std::shared_ptr<JsonPointerTestCase> > TestCases;
|
|
|
|
// Ensure memory used for test cases is freed when test function completes
|
|
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> allocator;
|
|
|
|
TestCases testCases = testCasesForSingleLevelObjectPointers(allocator);
|
|
|
|
for (const auto & testCase : testCases) {
|
|
const std::string &jsonPointer = testCase->jsonPointer;
|
|
const RapidJsonAdapter valueAdapter(testCase->value);
|
|
if (testCase->expectedValue) {
|
|
const RapidJsonAdapter expectedAdapter(*(testCase->expectedValue));
|
|
const RapidJsonAdapter actualAdapter = resolveJsonPointer(valueAdapter, jsonPointer);
|
|
EXPECT_TRUE(actualAdapter.equalTo(expectedAdapter, true)) << testCase->description;
|
|
} else {
|
|
// Since the tests with throwing disabled will abort, we can't
|
|
// do anything here.
|
|
|
|
#if VALIJSON_USE_EXCEPTIONS
|
|
EXPECT_THROW(resolveJsonPointer(valueAdapter, jsonPointer), std::runtime_error) << testCase->description;
|
|
#endif
|
|
}
|
|
}
|
|
}
|