Allow for redundant slashes in reference tokens, as per spec

This commit is contained in:
Tristan Penman 2015-05-06 14:33:14 +10:00
parent b4d7f76b25
commit 4f88bb8941
2 changed files with 52 additions and 26 deletions

View File

@ -41,31 +41,39 @@ template<typename AdapterType>
inline AdapterType resolveJsonPointer(
const AdapterType &node,
const std::string &jsonPointer,
std::string::const_iterator jsonPointerItr)
const std::string::const_iterator jsonPointerItr)
{
// TODO: This function will probably need to implement support for
// fetching documents referenced by JSON Pointers, similar to the
// populateSchema function.
const std::string::const_iterator jsonPointerEnd = jsonPointer.end();
// Terminate recursion if all reference tokens have been consumed
if (jsonPointerItr == jsonPointerEnd) {
// Bottom out recursion
return node;
}
// Find iterator that points to next slash, or end of string
const std::string::const_iterator jsonPointerNext =
std::find(jsonPointerItr, jsonPointerEnd, '/');
// Extract the next reference token
const std::string referenceToken(jsonPointerItr, jsonPointerNext);
if (referenceToken.empty()) {
throw std::runtime_error(
"Expected at least one non-delimiting character in the next "
"reference token.");
// Reference tokens must begin with a leading slash
if (*jsonPointerItr != '/') {
throw std::runtime_error("Expected reference token to begin with "
"leading slash; remaining tokens: " +
std::string(jsonPointerItr, jsonPointerEnd));
}
if (node.isArray()) {
// Find iterator that points to next slash or newline character; this is
// one character past the end of the current reference token
std::string::const_iterator jsonPointerNext =
std::find(jsonPointerItr + 1, jsonPointerEnd, '/');
// Extract the next reference token
const std::string referenceToken(jsonPointerItr + 1, jsonPointerNext);
// Empty reference tokens should be ignored
if (referenceToken.empty()) {
return resolveJsonPointer(node, jsonPointer, jsonPointerNext);
} else if (node.isArray()) {
try {
// Fragment must be non-negative integer
const uint64_t index = boost::lexical_cast<uint64_t>(jsonPointer);
@ -87,7 +95,8 @@ inline AdapterType resolveJsonPointer(
} else if (node.maybeObject()) {
// Fragment must identify a member of the candidate object
typedef typename AdapterType::Object Object;
typename Object::const_iterator itr = node.asObject().find(referenceToken);
typename Object::const_iterator itr = node.asObject().find(
referenceToken);
if (itr == node.asObject().end()) {
throw std::runtime_error("Expected reference token to identify an "
"element in the current object; "
@ -145,12 +154,7 @@ inline AdapterType resolveJsonPointer(
const AdapterType &rootNode,
const std::string &jsonPointer)
{
if (jsonPointer.find("/") != 0) {
throw std::runtime_error(
"Expected leading '/' while parsing JSON Pointer.");
}
return ::resolveJsonPointer(rootNode, jsonPointer, jsonPointer.begin() + 1);
return ::resolveJsonPointer(rootNode, jsonPointer, jsonPointer.begin());
}
} // namespace json_reference

View File

@ -52,10 +52,24 @@ std::vector<boost::shared_ptr<JsonPointerTestCase> >
testCases.push_back(testCase);
testCase = boost::make_shared<JsonPointerTestCase>(
"Resolving an empty string should cause an exception to be thrown");
"Resolving an empty string should return the root node");
testCase->value.SetNull();
testCase->jsonPointer = "";
testCase->expectedValue = NULL;
testCase->expectedValue = &testCase->value;
testCases.push_back(testCase);
testCase = boost::make_shared<JsonPointerTestCase>(
"Resolving '/' should return the root node");
testCase->value.SetNull();
testCase->jsonPointer = "/";
testCase->expectedValue = &testCase->value;
testCases.push_back(testCase);
testCase = boost::make_shared<JsonPointerTestCase>(
"Resolving '//' should return the root node");
testCase->value.SetNull();
testCase->jsonPointer = "//";
testCase->expectedValue = &testCase->value;
testCases.push_back(testCase);
testCase = boost::make_shared<JsonPointerTestCase>(
@ -67,12 +81,19 @@ std::vector<boost::shared_ptr<JsonPointerTestCase> >
testCases.push_back(testCase);
testCase = boost::make_shared<JsonPointerTestCase>(
"Resolve '/test/' in object containing one member named 'test' "
"should cause an exception to be thrown");
"Resolve '/test/' in object containing one member named 'test'");
testCase->value.SetObject();
testCase->value.AddMember("test", "test", allocator);
testCase->jsonPointer = "/test/";
testCase->expectedValue = NULL;
testCase->expectedValue = &testCase->value.FindMember("test")->value;
testCases.push_back(testCase);
testCase = boost::make_shared<JsonPointerTestCase>(
"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 = boost::make_shared<JsonPointerTestCase>(
@ -108,7 +129,8 @@ TEST_F(TestJsonReference, JsonPointerTestCases)
} else {
EXPECT_THROW(
resolveJsonPointer(valueAdapter, jsonPointer),
std::runtime_error);
std::runtime_error) <<
(*itr)->description;
}
}
}