mirror of
https://github.com/tristanpenman/valijson.git
synced 2025-01-31 14:39:53 +01:00
Merge branch 'schema-parser-ref-support-rewrite'
This commit is contained in:
commit
00d0fa8d43
16
README.md
16
README.md
@ -10,12 +10,6 @@ Valijson provides a simple validation API that allows you load JSON Schemas, and
|
||||
|
||||
The goal of this project is to support validation of all constraints available in JSON Schema v4, while being competitive with the performance of hand-written JSON validators.
|
||||
|
||||
### JSON References ###
|
||||
|
||||
The library is intended to include support for both local and remote JSON References. This feature is currently a work in progress and is subject to change. The current implementation attempts to resolve JSON References while parsing a JSON Schema, but this has proven ineffective for several use cases and loses some of the flexibility intended by the JSON Reference and JSON Schema specifications.
|
||||
|
||||
There is a branch of the project that is intended to fix these issues by implementing a two phase schema parser. This parser would first build a graph from a JSON document by resolving any references, then load a JSON Schema model by traversing that graph. This will hopefully be complete in the near future.
|
||||
|
||||
## Usage ##
|
||||
|
||||
The following code snippets show how you might implement a simple validator using RapidJson as the underlying JSON Parser.
|
||||
@ -108,6 +102,12 @@ Things get more interesting when you build a schema using custom code, as illust
|
||||
// Root schema goes out of scope and all allocated memory is freed
|
||||
}
|
||||
|
||||
## JSON References ##
|
||||
|
||||
The library includes support for local JSON References, as well as remote JSON References when the appropriate callback functions are provided.
|
||||
|
||||
Two callback functions are required. The first is expected to return a pointer to a newly fetched document. Valijson takes ownership of this pointer. The second callback function is used to release ownership of that pointer back to the application. Typically, this would immediately free the memory that was allocated for the document.
|
||||
|
||||
## Test Suite ##
|
||||
|
||||
Valijson's' test suite currently contains several hand-crafted tests and uses the standard [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) to test support for parts of the JSON Schema feature set that have been implemented.
|
||||
@ -147,15 +147,11 @@ The exceptions for Draft 3 are:
|
||||
- extends
|
||||
- format (optional)
|
||||
- readonly
|
||||
- ref
|
||||
- refRemote
|
||||
|
||||
The exceptions for Draft 4 are:
|
||||
|
||||
- definitions
|
||||
- format (optional)
|
||||
- ref
|
||||
- refRemote
|
||||
|
||||
Support for JSON References is in development.
|
||||
|
||||
|
@ -41,11 +41,10 @@ namespace 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
|
||||
* @return Optional string containing JSON Pointer
|
||||
*/
|
||||
inline std::string getJsonReferencePointer(const std::string &jsonRef)
|
||||
inline boost::optional<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
|
||||
@ -55,10 +54,10 @@ namespace json_reference {
|
||||
return jsonRef.substr(ptrPos + 1);
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
"JSON Reference value does not contain a valid JSON Pointer");
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
|
||||
} // namespace json_reference
|
||||
} // namespace internal
|
||||
} // namespace valijson
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,43 +13,40 @@ using valijson::SchemaParser;
|
||||
using valijson::adapters::RapidJsonAdapter;
|
||||
using valijson::Validator;
|
||||
|
||||
typedef SchemaParser::FetchDocumentFunction<RapidJsonAdapter>::Type
|
||||
FetchDocumentFunction;
|
||||
|
||||
namespace {
|
||||
|
||||
static rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> allocator;
|
||||
static rapidjson::Value fetchedRoot;
|
||||
static RapidJsonAdapter fetchedRootAdapter;
|
||||
|
||||
}
|
||||
|
||||
class TestFetchDocumentCallback : public ::testing::Test
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
boost::shared_ptr<const RapidJsonAdapter> fetchDocument(const std::string &uri)
|
||||
const rapidjson::Document * fetchDocument(const std::string &uri)
|
||||
{
|
||||
EXPECT_STREQ("test", uri.c_str());
|
||||
EXPECT_STREQ("http://localhost:1234/", uri.c_str());
|
||||
|
||||
rapidjson::Document *fetchedRoot = new rapidjson::Document();
|
||||
fetchedRoot->SetObject();
|
||||
|
||||
rapidjson::Value valueOfTypeAttribute;
|
||||
valueOfTypeAttribute.SetString("string", allocator);
|
||||
valueOfTypeAttribute.SetString("string", fetchedRoot->GetAllocator());
|
||||
|
||||
rapidjson::Value schemaOfTestProperty;
|
||||
schemaOfTestProperty.SetObject();
|
||||
schemaOfTestProperty.AddMember("type", valueOfTypeAttribute, allocator);
|
||||
schemaOfTestProperty.AddMember("type", valueOfTypeAttribute,
|
||||
fetchedRoot->GetAllocator());
|
||||
|
||||
rapidjson::Value propertiesConstraint;
|
||||
propertiesConstraint.SetObject();
|
||||
propertiesConstraint.AddMember("test", schemaOfTestProperty, allocator);
|
||||
propertiesConstraint.AddMember("test", schemaOfTestProperty,
|
||||
fetchedRoot->GetAllocator());
|
||||
|
||||
fetchedRoot.SetObject();
|
||||
fetchedRoot.AddMember("properties", propertiesConstraint, allocator);
|
||||
fetchedRoot->AddMember("properties", propertiesConstraint,
|
||||
fetchedRoot->GetAllocator());
|
||||
|
||||
// Have to ensure that fetchedRoot exists for at least as long as the
|
||||
// shared pointer that we return here
|
||||
return boost::make_shared<RapidJsonAdapter>(fetchedRoot);
|
||||
return fetchedRoot;
|
||||
}
|
||||
|
||||
void freeDocument(const rapidjson::Document *adapter)
|
||||
{
|
||||
delete adapter;
|
||||
}
|
||||
|
||||
TEST_F(TestFetchDocumentCallback, Basics)
|
||||
@ -58,24 +55,27 @@ TEST_F(TestFetchDocumentCallback, Basics)
|
||||
rapidjson::Document schemaDocument;
|
||||
RapidJsonAdapter schemaDocumentAdapter(schemaDocument);
|
||||
schemaDocument.SetObject();
|
||||
schemaDocument.AddMember("$ref", "test#/", allocator);
|
||||
schemaDocument.AddMember("$ref", "http://localhost:1234/#/",
|
||||
schemaDocument.GetAllocator());
|
||||
|
||||
// Parse schema document
|
||||
Schema schema;
|
||||
SchemaParser schemaParser;
|
||||
schemaParser.populateSchema(schemaDocumentAdapter, schema,
|
||||
boost::make_optional<FetchDocumentFunction>(fetchDocument));
|
||||
schemaParser.populateSchema(schemaDocumentAdapter, schema, fetchDocument,
|
||||
freeDocument);
|
||||
|
||||
// Test resulting schema with a valid document
|
||||
rapidjson::Document validDocument;
|
||||
validDocument.SetObject();
|
||||
validDocument.AddMember("test", "valid", allocator);
|
||||
validDocument.AddMember("test", "valid", schemaDocument.GetAllocator());
|
||||
Validator validator;
|
||||
EXPECT_TRUE(validator.validate(schema, RapidJsonAdapter(validDocument), NULL));
|
||||
EXPECT_TRUE(validator.validate(schema, RapidJsonAdapter(validDocument),
|
||||
NULL));
|
||||
|
||||
// Test resulting schema with an invalid document
|
||||
rapidjson::Document invalidDocument;
|
||||
invalidDocument.SetObject();
|
||||
invalidDocument.AddMember("test", 123, allocator);
|
||||
EXPECT_FALSE(validator.validate(schema, RapidJsonAdapter(invalidDocument), NULL));
|
||||
invalidDocument.AddMember("test", 123, schemaDocument.GetAllocator());
|
||||
EXPECT_FALSE(validator.validate(schema, RapidJsonAdapter(invalidDocument),
|
||||
NULL));
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include <valijson/utils/json11_utils.hpp>
|
||||
#endif // VALIJSON_BUILD_CXX11_ADAPTERS
|
||||
|
||||
#define REMOTES_DIR "../thirdparty/JSON-Schema-Test-Suite/remotes/"
|
||||
|
||||
#define TEST_SUITE_DIR "../thirdparty/JSON-Schema-Test-Suite/tests/"
|
||||
|
||||
using valijson::adapters::AdapterTraits;
|
||||
@ -31,6 +33,52 @@ using valijson::Schema;
|
||||
using valijson::SchemaParser;
|
||||
using valijson::Validator;
|
||||
|
||||
std::string getRelativePath(const std::string &uri)
|
||||
{
|
||||
const std::string dummyUri = "http://localhost:1234/";
|
||||
size_t n = uri.find(dummyUri);
|
||||
if (n != std::string::npos) {
|
||||
return REMOTES_DIR + uri.substr(dummyUri.size());
|
||||
}
|
||||
|
||||
const std::string v3SchemaUri = "http://json-schema.org/draft-03/schema";
|
||||
n = uri.find(v3SchemaUri);
|
||||
if (n != std::string::npos) {
|
||||
return "../doc/schema/draft-03.json";
|
||||
}
|
||||
|
||||
const std::string v4SchemaUri = "http://json-schema.org/draft-04/schema";
|
||||
n = uri.find(v4SchemaUri);
|
||||
if (n != std::string::npos) {
|
||||
return "../doc/schema/draft-04.json";
|
||||
}
|
||||
|
||||
throw std::runtime_error("Attempt fetchDoc of " + uri);
|
||||
}
|
||||
|
||||
template<typename AdapterType>
|
||||
const typename AdapterTraits<AdapterType>::DocumentType * fetchDocument(
|
||||
const std::string &uri)
|
||||
{
|
||||
const std::string relativePath = getRelativePath(uri);
|
||||
|
||||
typename AdapterTraits<AdapterType>::DocumentType *document =
|
||||
new typename AdapterTraits<AdapterType>::DocumentType();
|
||||
|
||||
if (!valijson::utils::loadDocument(relativePath, *document)) {
|
||||
delete document;
|
||||
throw std::runtime_error("Failed fetchDoc of " + uri);
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
template<typename AdapterType>
|
||||
void freeDocument(const typename AdapterTraits<AdapterType>::DocumentType *ptr)
|
||||
{
|
||||
delete ptr;
|
||||
}
|
||||
|
||||
class TestValidator : public ::testing::TestWithParam<const char *>
|
||||
{
|
||||
protected:
|
||||
@ -72,7 +120,8 @@ protected:
|
||||
// Parse schema
|
||||
Schema schema;
|
||||
SchemaParser parser(version);
|
||||
parser.populateSchema(itr->second, schema);
|
||||
parser.populateSchema(itr->second, schema,
|
||||
fetchDocument<AdapterType>, freeDocument<AdapterType>);
|
||||
|
||||
// For each test in the 'tests' array
|
||||
itr = object.find("tests");
|
||||
@ -212,6 +261,16 @@ TEST_F(TestValidator, Draft3_Properties)
|
||||
processDraft3TestFile(TEST_SUITE_DIR "draft3/properties.json");
|
||||
}
|
||||
|
||||
TEST_F(TestValidator, Draft3_Ref)
|
||||
{
|
||||
processDraft3TestFile(TEST_SUITE_DIR "draft3/ref.json");
|
||||
}
|
||||
|
||||
TEST_F(TestValidator, Draft3_RefRemote)
|
||||
{
|
||||
processDraft3TestFile(TEST_SUITE_DIR "draft3/refRemote.json");
|
||||
}
|
||||
|
||||
TEST_F(TestValidator, Draft3_Required)
|
||||
{
|
||||
processDraft3TestFile(TEST_SUITE_DIR "draft3/required.json");
|
||||
@ -332,6 +391,16 @@ TEST_F(TestValidator, Draft4_Properties)
|
||||
processDraft4TestFile(TEST_SUITE_DIR "draft4/properties.json");
|
||||
}
|
||||
|
||||
TEST_F(TestValidator, Draft4_Ref)
|
||||
{
|
||||
processDraft4TestFile(TEST_SUITE_DIR "draft4/ref.json");
|
||||
}
|
||||
|
||||
TEST_F(TestValidator, Draft4_RefRemote)
|
||||
{
|
||||
processDraft4TestFile(TEST_SUITE_DIR "draft4/refRemote.json");
|
||||
}
|
||||
|
||||
TEST_F(TestValidator, Draft4_Required)
|
||||
{
|
||||
processDraft4TestFile(TEST_SUITE_DIR "draft4/required.json");
|
||||
|
Loading…
x
Reference in New Issue
Block a user