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

View File

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