validator: handle undefined schema-loader via a std::function given at construction-time

This commit is contained in:
Patrick Boettcher 2016-12-27 11:39:48 +01:00
parent 685b759712
commit 998f97ba4e
4 changed files with 87 additions and 112 deletions

View File

@ -31,19 +31,37 @@ using nlohmann::json;
using nlohmann::json_uri;
using nlohmann::json_schema_draft4::json_validator;
static void loader(const json_uri &uri, json &schema)
{
std::map<std::string, std::string> external_schemas =
{
{"http://localhost:1234/integer.json", JSON_SCHEMA_TEST_SUITE_PATH "/remotes/integer.json"},
{"http://localhost:1234/subSchemas.json", JSON_SCHEMA_TEST_SUITE_PATH "/remotes/subSchemas.json"},
{"http://localhost:1234/folder/folderInteger.json", JSON_SCHEMA_TEST_SUITE_PATH "/remotes/folder/folderInteger.json"},
};
if (uri.to_string() == "http://json-schema.org/draft-04/schema#") {
schema = nlohmann::json_schema_draft4::draft4_schema_builtin;
return;
}
std::string fn = external_schemas[uri.url()];
std::fstream s(fn.c_str());
if (!s.good())
throw std::invalid_argument("could not open " + uri.url() + " for schema loading\n");
try {
schema << s;
} catch (std::exception &e) {
throw e;
}
}
int main(void)
{
json validation; // a validation case following the JSON-test-suite-schema
std::map<std::string, std::string> external_schemas;
external_schemas["http://localhost:1234/integer.json"] =
JSON_SCHEMA_TEST_SUITE_PATH "/remotes/integer.json";
external_schemas["http://localhost:1234/subSchemas.json"] =
JSON_SCHEMA_TEST_SUITE_PATH "/remotes/subSchemas.json";
external_schemas["http://localhost:1234/folder/folderInteger.json"] =
JSON_SCHEMA_TEST_SUITE_PATH "/remotes/folder/folderInteger.json";
try {
std::cin >> validation;
} catch (std::exception &e) {
@ -62,44 +80,7 @@ int main(void)
const auto &schema = test_group["schema"];
json_validator validator;
do {
std::set<json_uri> undefined;
try {
undefined = validator.insert_schema(schema, json_uri("#"));
} catch (std::exception &e) {
std::cout << " Test Case Exception (root-schema-inserting): " << e.what() << "\n";
}
if (undefined.size() == 0)
break;
for (auto ref : undefined) {
std::cerr << "missing schema URL " << ref << " - trying to load it\n";
if (ref.to_string() == "http://json-schema.org/draft-04/schema#")
validator.insert_schema(nlohmann::json_schema_draft4::draft4_schema_builtin, ref);
else {
std::string fn = external_schemas[ref.url()];
std::fstream s(fn.c_str());
if (!s.good()) {
std::cerr << "could not open " << ref.url() << "\n";
return EXIT_FAILURE;
}
json extra;
extra << s;
try {
validator.insert_schema(extra, ref.url());
} catch (std::exception &e) {
std::cout << " Test Case Exception (schema-loading/inserting): " << e.what() << "\n";
}
}
}
} while (1);
json_validator validator(schema, loader);
for (auto &test_case : test_group["tests"]) {
std::cout << " Testing Case " << test_case["description"] << "\n";
@ -109,17 +90,14 @@ int main(void)
try {
validator.validate(test_case["data"]);
} catch (const std::out_of_range &e) {
valid = false;
std::cout << " Test Case Exception (out of range): " << e.what() << "\n";
} catch (const std::invalid_argument &e) {
valid = false;
std::cout << " Test Case Exception (invalid argument): " << e.what() << "\n";
} catch (const std::logic_error &e) {
valid = !test_case["valid"]; /* force test-case failure */
std::cout << " Not yet implemented: " << e.what() << "\n";
}

View File

@ -46,12 +46,24 @@ static void usage(const char *name)
assert(r.undefined_refs.size() == 0);
#endif
static void loader(const json_uri &uri, json &schema)
{
std::fstream lf(uri.path());
if (!lf.good())
throw std::invalid_argument("could not open " + uri.url());
try {
lf >> schema;
} catch (std::exception &e) {
throw e;
}
}
int main(int argc, char *argv[])
{
if (argc != 2)
usage(argv[0]);
json_validator validator;
std::fstream f(argv[1]);
if (!f.good()) {
@ -69,41 +81,10 @@ int main(int argc, char *argv[])
}
// 2) insert this schema to the validator
// this resolves remote-schemas, sub-schemas and references
bool error = false;
do {
// inserting with json_uri("#") means this is the document's root-schema
auto missing_schemas = validator.insert_schema(schema, json_uri("#"));
// this resolves remote-schemas, sub-schemas and references via the given loader-function
json_validator validator(schema, loader);
// schema was inserted and all references have been fulfilled
if (missing_schemas.size() == 0)
break;
// schema was not inserted because it references unknown schemas
// 3) load missing schemas and insert them
for (auto ref : missing_schemas) {
std::cerr << "missing schema URL " << ref << " - trying to load it\n";
std::fstream lf(ref.path());
if (!lf.good()) {
std::cerr << "could not open " << ref.url() << "\n";
error = true;
break;
}
json extra;
try {
lf >> extra;
} catch (std::exception &e) {
std::cerr << e.what() << " at " << lf.tellp() << "\n";
return EXIT_FAILURE;
}
validator.insert_schema(extra, json_uri(ref.url()));
std::cerr << "OK";
}
} while (!error);
// 4) do the actual validation of the document
// 3) do the actual validation of the document
json document;
try {

View File

@ -28,8 +28,6 @@
#include <json.hpp>
#include <set>
// make yourself a home - welcome to nlohmann's namespace
namespace nlohmann
{
@ -161,6 +159,7 @@ class json_validator
{
std::vector<std::shared_ptr<json>> schema_store_;
std::shared_ptr<json> root_schema_;
std::function<void(const json_uri &, json &)> schema_loader_ = nullptr;
std::map<json_uri, const json *> schema_refs_;
@ -168,8 +167,10 @@ class json_validator
void validate_array(json &instance, const json &schema_, const std::string &name);
void validate_object(json &instance, const json &schema_, const std::string &name);
void insert_schema(const json &input, const json_uri &id);
public:
std::set<json_uri> insert_schema(const json &input, json_uri id);
json_validator(const json &schema, std::function<void(const json_uri &, json &)> loader);
void validate(json &instance);

View File

@ -271,43 +271,51 @@ namespace nlohmann
namespace json_schema_draft4
{
std::set<json_uri> json_validator::insert_schema(const json &input, json_uri id)
void json_validator::insert_schema(const json &input, const json_uri &id)
{
// allocate create a copy for later storage - if resolving reference works
std::shared_ptr<json> schema = std::make_shared<json>(input);
// resolve all local schemas and references
resolver r(*schema, id);
do {
// resolve all local schemas and references
resolver r(*schema, id);
// check whether all undefined schema references can be resolved with existing ones
std::set<json_uri> undefined;
for (auto &ref : r.undefined_refs)
if (schema_refs_.find(ref) == schema_refs_.end()) { // exact schema reference not found
undefined.insert(ref);
// check whether all undefined schema references can be resolved with existing ones
std::set<json_uri> undefined;
for (auto &ref : r.undefined_refs)
if (schema_refs_.find(ref) == schema_refs_.end()) // exact schema reference not found
undefined.insert(ref);
if (undefined.size() == 0) { // no undefined references
// now insert all schema-references
// check whether all schema-references are new
for (auto &sref : r.schema_refs) {
if (schema_refs_.find(sref.first) != schema_refs_.end())
throw std::invalid_argument("schema " + sref.first.to_string() + " already present in validator.");
}
// no undefined references and no duplicated schema - store the schema
schema_store_.push_back(schema);
// and insert all references
schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end());
break;
}
// anything cannot be resolved, inform the user and make him/her load additional schemas
// before retrying
if (undefined.size() > 0)
return undefined;
if (schema_loader_ == nullptr)
throw std::invalid_argument("schema contains undefined references to other schemas, needed schema-loader.");
// check whether all schema-references are new
for (auto &sref : r.schema_refs) {
if (schema_refs_.find(sref.first) != schema_refs_.end())
throw std::invalid_argument("schema " + sref.first.to_string() + " already present in validator.");
}
for (auto undef : undefined) {
json ext;
// no undefined references and no duplicated schema - store the schema
schema_store_.push_back(schema);
// and insert all references
schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end());
schema_loader_(undef, ext);
insert_schema(ext, undef.url());
}
} while (1);
// store the document root-schema
if (id == json_uri("#"))
root_schema_ = schema;
return undefined;
}
void json_validator::validate(json &instance)
@ -318,10 +326,17 @@ void json_validator::validate(json &instance)
validate(instance, *root_schema_, "root");
}
json_validator::json_validator(const json &schema, std::function<void(const json_uri &, json &)> loader)
: schema_loader_(loader)
{
insert_schema(schema, json_uri("#"));
}
void json_validator::validate(json &instance, const json &schema_, const std::string &name)
{
const json *schema = &schema_;
// $ref resolution
do {
const auto &ref = schema->find("$ref");
if (ref == schema->end())