Lift rough JSON Pointer resolution functions into separate file

This commit is contained in:
Tristan Penman 2015-05-05 16:18:21 +10:00
parent e8bb484011
commit f42a4f9458
5 changed files with 239 additions and 48 deletions

View File

@ -0,0 +1,111 @@
#ifndef __VALIJSON_JSON_REFERENCE_HPP
#define __VALIJSON_JSON_REFERENCE_HPP
#include <stdexcept>
#include <string>
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
#include <valijson/adapters/adapter.hpp>
namespace valijson {
namespace internal {
namespace json_reference {
/**
* @brief Extract JSON Pointer portion of a JSON Reference
*
* @param jsonRef JSON Reference to extract from
*
* @return string containing JSON Pointer
*
* @throw std::runtime_error if the string does not contain a JSON Pointer
*/
inline std::string getJsonReferencePointer(const std::string &jsonRef)
{
// Attempt to extract JSON Pointer if '#' character is present. Note
// that a valid pointer would contain at least a leading forward
// slash character.
const size_t ptrPos = jsonRef.find("#");
if (ptrPos != std::string::npos) {
return jsonRef.substr(ptrPos + 1);
}
throw std::runtime_error(
"JSON Reference value does not contain a valid JSON Pointer");
}
/**
* @brief Return reference to part of document referenced by JSON Pointer
*
* @param node node to use as root for JSON Pointer resolution
* @param jsonPointer string containing JSON Pointer
*
* @return reference to an instance AdapterType in the specified document
*/
template<typename AdapterType>
inline AdapterType resolveJsonPointer(const AdapterType &node,
std::string jsonPointer)
{
// TODO: This function will probably need to implement support for
// fetching documents referenced by JSON Pointers, similar to the
// populateSchema function.
// Check for leading forward slash
if (jsonPointer.find("/") != 0) {
throw std::runtime_error(
"JSON Pointer must begin with reference to root node");
}
// Remove leading slash
jsonPointer = jsonPointer.substr(1);
// Recursion bottoms out here
if (jsonPointer.empty()) {
return node;
}
// Extract next directive
const std::string directive = jsonPointer.substr(1, jsonPointer.find("/"));
if (directive.empty()) {
throw std::runtime_error(
"JSON Pointer contains zero-length directive");
}
// Remove directive from remainder of JSON pointer
jsonPointer = jsonPointer.substr(directive.length());
if (node.isArray()) {
try {
// Fragment must be non-negative integer
const uint64_t index = boost::lexical_cast<uint64_t>(jsonPointer);
typedef typename AdapterType::Array Array;
typename Array::const_iterator itr = node.asArray().begin();
itr.advance(index);
// TODO: Check for array bounds
return resolveJsonPointer(*itr, jsonPointer);
} catch (boost::bad_lexical_cast &) {
throw std::runtime_error("Invalid array index in JSON Reference: " +
directive);
}
} else if (node.maybeObject()) {
typedef typename AdapterType::Object Object;
typename Object::const_iterator itr = node.asObject().find(directive);
if (itr == node.asObject().end()) {
throw std::runtime_error("Could not find element");
}
return resolveJsonPointer(itr->second, jsonPointer);
}
throw std::runtime_error("Directive applied to simple value type");
}
} // namespace json_reference
} // namespace internal
} // namespace valijson
#endif

View File

@ -11,6 +11,7 @@
#include <valijson/adapters/adapter.hpp>
#include <valijson/constraints/concrete_constraints.hpp>
#include <valijson/internal/json_reference.hpp>
#include <valijson/schema.hpp>
namespace valijson {
@ -275,44 +276,16 @@ private:
*
* @return Optional string containing URI
*/
static boost::optional<std::string> getJsonReferenceUri(
inline boost::optional<std::string> getJsonReferenceUri(
const std::string &jsonRef,
const Schema &schema)
{
return schema.resolveUri(jsonRef);
}
/**
* @brief Extract JSON Pointer portion of a JSON Reference
*
* @param jsonRef JSON Reference to extract from
*
* @return Optional string containing JSON Pointer
*/
static boost::optional<std::string> getJsonReferencePointer(
const std::string &jsonRef)
{
return std::string();
}
/**
* @brief Return reference to part of document referenced by JSON Pointer
*
* @param node node to use as root for JSON Pointer resolution
* @param jsonPointer string containing JSON Pointer
*
* @return reference to an instance AdapterType in the specified document
*/
template<typename AdapterType>
const AdapterType & resolveJsonPointer(
const AdapterType &node,
const std::string &jsonPointer)
{
// TODO: Complete functionality
// TODO: This function will probably need to implement support for
// fetching documents referenced by JSON Pointers, similar to the
// populateSchema function.
return node;
const size_t ptrPos = jsonRef.find("#");
if (ptrPos != std::string::npos) {
return schema.resolveUri(jsonRef.substr(0, ptrPos));
} else {
return schema.resolveUri(jsonRef);
}
}
/**
@ -346,12 +319,8 @@ private:
getJsonReferenceUri(jsonRef, schema);
// Extract JSON Pointer from JSON Reference
const boost::optional<std::string> jsonPointer =
getJsonReferencePointer(jsonRef);
if (!jsonPointer) {
throw std::runtime_error(
"Failed to parse JSON pointer");
}
const std::string jsonPointer =
internal::json_reference::getJsonReferencePointer(jsonRef);
if (documentUri) {
// Resolve reference against remote document
@ -372,14 +341,20 @@ private:
"Failed to fetch referenced schema document.");
}
const AdapterType &ref =
internal::json_reference::resolveJsonPointer(*docPtr,
jsonPointer);
// Resolve reference against retrieved document
const AdapterType &ref = resolveJsonPointer(*docPtr, *jsonPointer);
populateSchema<AdapterType>(ref, schema, fetchDoc, parentSchema,
ownName);
} else {
const AdapterType &ref =
internal::json_reference::resolveJsonPointer(node,
jsonPointer);
// Resolve reference against current document
const AdapterType &ref = resolveJsonPointer(node, *jsonPointer);
populateSchema<AdapterType>(ref, schema, fetchDoc, parentSchema,
ownName);
}
@ -990,7 +965,8 @@ private:
const AdapterType *properties,
const AdapterType *patternProperties,
const AdapterType *additionalProperties,
boost::optional<typename FetchDocumentFunction<AdapterType>::Type > fetchDoc,
boost::optional<typename FetchDocumentFunction<AdapterType>::Type >
fetchDoc,
Schema *parentSchema)
{
typedef typename AdapterType::ObjectMember Member;

View File

@ -31,9 +31,7 @@ class TestFetchDocumentCallback : public ::testing::Test
boost::shared_ptr<const RapidJsonAdapter> fetchDocument(const std::string &uri)
{
if (uri.compare("test") != 0) {
throw std::runtime_error("Could not resolve reference");
}
EXPECT_STREQ("test", uri.c_str());
rapidjson::Value valueOfTypeAttribute;
valueOfTypeAttribute.SetString("string", allocator);
@ -60,7 +58,7 @@ TEST_F(TestFetchDocumentCallback, Basics)
rapidjson::Document schemaDocument;
RapidJsonAdapter schemaDocumentAdapter(schemaDocument);
schemaDocument.SetObject();
schemaDocument.AddMember("$ref", "test", allocator);
schemaDocument.AddMember("$ref", "test#/", allocator);
// Parse schema document
Schema schema;

View File

@ -0,0 +1,46 @@
#include <gtest/gtest.h>
#include <valijson/internal/json_reference.hpp>
#include <valijson/adapters/rapidjson_adapter.hpp>
using valijson::adapters::RapidJsonAdapter;
using valijson::internal::json_reference::resolveJsonPointer;
namespace {
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> allocator;
}
class TestJsonReference : public testing::Test
{
};
TEST_F(TestJsonReference, PointerWithoutLeadingSlashShouldThrow)
{
// Given an Adapter for a JSON object
rapidjson::Value value;
value.SetObject();
const RapidJsonAdapter rootAdapter(value);
// When a JSON Pointer that does not begin with a slash is resolved
// Then an exception should be thrown
EXPECT_THROW(resolveJsonPointer(rootAdapter, "#"), std::runtime_error);
}
TEST_F(TestJsonReference, RootPointer)
{
// Given an Adapter for a JSON object containing one attribute
rapidjson::Value value;
value.SetObject();
value.AddMember("test", "test", allocator);
const RapidJsonAdapter rootAdapter(value);
// When a JSON Pointer that points to the root node is resolved
const RapidJsonAdapter resultAdapter = resolveJsonPointer(rootAdapter, "/");
// Then the returned Adapter should also refer to the root node
EXPECT_TRUE(resultAdapter.isObject());
const RapidJsonAdapter::Object object = resultAdapter.asObject();
EXPECT_NE(object.end(), object.find("test"));
}

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
6A477F8517D6BCBB0013571C /* libboost_regex-mt.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A477F8417D6BCBB0013571C /* libboost_regex-mt.dylib */; };
6A477F8617D6EA000013571C /* libboost_regex-mt.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A477F8417D6BCBB0013571C /* libboost_regex-mt.dylib */; };
6A506D201AF88E5D00C2C818 /* test_json_reference.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A506D1F1AF88E5D00C2C818 /* test_json_reference.cpp */; };
6A725F4517F61D7000D6B2FF /* test_validation_errors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A725F4317F61B5100D6B2FF /* test_validation_errors.cpp */; };
6A725F4617F6404100D6B2FF /* test_adapter_comparison.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6AB8FEB117E6DF9A0028E147 /* test_adapter_comparison.cpp */; };
6A725F4717F6404100D6B2FF /* test_jsoncpp_adapter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6AB8FE9217E6BE770028E147 /* test_jsoncpp_adapter.cpp */; };
@ -40,6 +41,18 @@
/* Begin PBXFileReference section */
6A477F8417D6BCBB0013571C /* libboost_regex-mt.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libboost_regex-mt.dylib"; path = "/usr/local/lib/libboost_regex-mt.dylib"; sourceTree = "<absolute>"; };
6A506D121AF884E100C2C818 /* draft-03.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "draft-03.json"; sourceTree = "<group>"; };
6A506D131AF884E100C2C818 /* draft-04.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "draft-04.json"; sourceTree = "<group>"; };
6A506D151AF884E100C2C818 /* draft-fge-json-schema-validation-00.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "draft-fge-json-schema-validation-00.txt"; sourceTree = "<group>"; };
6A506D161AF884E100C2C818 /* draft-luff-json-hyper-schema-00.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "draft-luff-json-hyper-schema-00.txt"; sourceTree = "<group>"; };
6A506D171AF884E100C2C818 /* draft-pbryan-zyp-json-ref-03.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "draft-pbryan-zyp-json-ref-03.txt"; sourceTree = "<group>"; };
6A506D181AF884E100C2C818 /* draft-zyp-json-schema-03.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "draft-zyp-json-schema-03.txt"; sourceTree = "<group>"; };
6A506D191AF884E100C2C818 /* draft-zyp-json-schema-04.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "draft-zyp-json-schema-04.txt"; sourceTree = "<group>"; };
6A506D1A1AF884E100C2C818 /* rfc3986-uri.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "rfc3986-uri.txt"; sourceTree = "<group>"; };
6A506D1B1AF884E100C2C818 /* rfc4627-json.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "rfc4627-json.txt"; sourceTree = "<group>"; };
6A506D1C1AF884E100C2C818 /* rfc6901-json-pointer.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "rfc6901-json-pointer.txt"; sourceTree = "<group>"; };
6A506D1E1AF88D8700C2C818 /* json_reference.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = json_reference.hpp; path = internal/json_reference.hpp; sourceTree = "<group>"; };
6A506D1F1AF88E5D00C2C818 /* test_json_reference.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test_json_reference.cpp; sourceTree = "<group>"; };
6A725F3617F61A4400D6B2FF /* array_doubles_10_20_30_40.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = array_doubles_10_20_30_40.json; sourceTree = "<group>"; };
6A725F3717F61A4400D6B2FF /* array_doubles_1_2_3.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = array_doubles_1_2_3.json; sourceTree = "<group>"; };
6A725F3817F61A4400D6B2FF /* array_doubles_1_2_3_4.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = array_doubles_1_2_3_4.json; sourceTree = "<group>"; };
@ -273,6 +286,49 @@
name = Frameworks;
sourceTree = "<group>";
};
6A506D101AF884D700C2C818 /* doc */ = {
isa = PBXGroup;
children = (
6A506D111AF884E100C2C818 /* schema */,
6A506D141AF884E100C2C818 /* specifications */,
);
name = doc;
sourceTree = "<group>";
};
6A506D111AF884E100C2C818 /* schema */ = {
isa = PBXGroup;
children = (
6A506D121AF884E100C2C818 /* draft-03.json */,
6A506D131AF884E100C2C818 /* draft-04.json */,
);
name = schema;
path = ../doc/schema;
sourceTree = "<group>";
};
6A506D141AF884E100C2C818 /* specifications */ = {
isa = PBXGroup;
children = (
6A506D151AF884E100C2C818 /* draft-fge-json-schema-validation-00.txt */,
6A506D161AF884E100C2C818 /* draft-luff-json-hyper-schema-00.txt */,
6A506D171AF884E100C2C818 /* draft-pbryan-zyp-json-ref-03.txt */,
6A506D181AF884E100C2C818 /* draft-zyp-json-schema-03.txt */,
6A506D191AF884E100C2C818 /* draft-zyp-json-schema-04.txt */,
6A506D1A1AF884E100C2C818 /* rfc3986-uri.txt */,
6A506D1B1AF884E100C2C818 /* rfc4627-json.txt */,
6A506D1C1AF884E100C2C818 /* rfc6901-json-pointer.txt */,
);
name = specifications;
path = ../doc/specifications;
sourceTree = "<group>";
};
6A506D1D1AF88D5E00C2C818 /* internal */ = {
isa = PBXGroup;
children = (
6A506D1E1AF88D8700C2C818 /* json_reference.hpp */,
);
name = internal;
sourceTree = "<group>";
};
6A725F3517F61A4400D6B2FF /* documents */ = {
isa = PBXGroup;
children = (
@ -612,6 +668,7 @@
6AB8FEB417E6E53D0028E147 /* data */,
6AB8FEB117E6DF9A0028E147 /* test_adapter_comparison.cpp */,
6AA8A5DA17F8BDCA002728A0 /* test_fetch_document_callback.cpp */,
6A506D1F1AF88E5D00C2C818 /* test_json_reference.cpp */,
6AB8FE9217E6BE770028E147 /* test_jsoncpp_adapter.cpp */,
6AB8FEC417E92B100028E147 /* test_property_tree_adapter.cpp */,
6AC18D3917CC874100FE0EC9 /* test_rapidjson_adapter.cpp */,
@ -626,6 +683,7 @@
6AC78AC417C5FBBC00674114 = {
isa = PBXGroup;
children = (
6A506D101AF884D700C2C818 /* doc */,
6AC78AEC17C5FC0700674114 /* examples */,
6AC78BDC17C5FC5F00674114 /* include */,
6AC18D3217CC869D00FE0EC9 /* tests */,
@ -670,6 +728,7 @@
children = (
6AC78BDE17C5FC6A00674114 /* adapters */,
6AC78BE317C5FC6A00674114 /* constraints */,
6A506D1D1AF88D5E00C2C818 /* internal */,
6A869A2F17CD8A81006864FA /* utils */,
6AC78BE917C5FC6A00674114 /* schema.hpp */,
6AC78BEA17C5FC6A00674114 /* schema_parser.hpp */,
@ -910,6 +969,7 @@
6A725F4717F6404100D6B2FF /* test_jsoncpp_adapter.cpp in Sources */,
6A725F4817F6404100D6B2FF /* test_property_tree_adapter.cpp in Sources */,
6AD3490118FF56FB004BDEE7 /* gtest_main.cc in Sources */,
6A506D201AF88E5D00C2C818 /* test_json_reference.cpp in Sources */,
6A725F4917F6404100D6B2FF /* test_rapidjson_adapter.cpp in Sources */,
6A725F4A17F6404100D6B2FF /* test_validator.cpp in Sources */,
6A725F4D17F8964B00D6B2FF /* test_uri_resolution.cpp in Sources */,