mirror of
https://github.com/tristanpenman/valijson.git
synced 2024-12-12 18:20:27 +01:00
Lift rough JSON Pointer resolution functions into separate file
This commit is contained in:
parent
e8bb484011
commit
f42a4f9458
111
include/valijson/internal/json_reference.hpp
Normal file
111
include/valijson/internal/json_reference.hpp
Normal 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
|
@ -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)
|
||||
{
|
||||
const size_t ptrPos = jsonRef.find("#");
|
||||
if (ptrPos != std::string::npos) {
|
||||
return schema.resolveUri(jsonRef.substr(0, ptrPos));
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
|
46
tests/test_json_reference.cpp
Normal file
46
tests/test_json_reference.cpp
Normal 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"));
|
||||
}
|
@ -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 */,
|
||||
|
Loading…
Reference in New Issue
Block a user