validator: handle undefined schema-loader via a std::function given at construction-time
This commit is contained in:
parent
685b759712
commit
998f97ba4e
@ -31,19 +31,37 @@ using nlohmann::json;
|
|||||||
using nlohmann::json_uri;
|
using nlohmann::json_uri;
|
||||||
using nlohmann::json_schema_draft4::json_validator;
|
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)
|
int main(void)
|
||||||
{
|
{
|
||||||
json validation; // a validation case following the JSON-test-suite-schema
|
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 {
|
try {
|
||||||
std::cin >> validation;
|
std::cin >> validation;
|
||||||
} catch (std::exception &e) {
|
} catch (std::exception &e) {
|
||||||
@ -62,44 +80,7 @@ int main(void)
|
|||||||
|
|
||||||
const auto &schema = test_group["schema"];
|
const auto &schema = test_group["schema"];
|
||||||
|
|
||||||
json_validator validator;
|
json_validator validator(schema, loader);
|
||||||
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);
|
|
||||||
|
|
||||||
for (auto &test_case : test_group["tests"]) {
|
for (auto &test_case : test_group["tests"]) {
|
||||||
std::cout << " Testing Case " << test_case["description"] << "\n";
|
std::cout << " Testing Case " << test_case["description"] << "\n";
|
||||||
@ -109,17 +90,14 @@ int main(void)
|
|||||||
try {
|
try {
|
||||||
validator.validate(test_case["data"]);
|
validator.validate(test_case["data"]);
|
||||||
} catch (const std::out_of_range &e) {
|
} catch (const std::out_of_range &e) {
|
||||||
|
|
||||||
valid = false;
|
valid = false;
|
||||||
std::cout << " Test Case Exception (out of range): " << e.what() << "\n";
|
std::cout << " Test Case Exception (out of range): " << e.what() << "\n";
|
||||||
|
|
||||||
} catch (const std::invalid_argument &e) {
|
} catch (const std::invalid_argument &e) {
|
||||||
|
|
||||||
valid = false;
|
valid = false;
|
||||||
std::cout << " Test Case Exception (invalid argument): " << e.what() << "\n";
|
std::cout << " Test Case Exception (invalid argument): " << e.what() << "\n";
|
||||||
|
|
||||||
} catch (const std::logic_error &e) {
|
} catch (const std::logic_error &e) {
|
||||||
|
|
||||||
valid = !test_case["valid"]; /* force test-case failure */
|
valid = !test_case["valid"]; /* force test-case failure */
|
||||||
std::cout << " Not yet implemented: " << e.what() << "\n";
|
std::cout << " Not yet implemented: " << e.what() << "\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,12 +46,24 @@ static void usage(const char *name)
|
|||||||
assert(r.undefined_refs.size() == 0);
|
assert(r.undefined_refs.size() == 0);
|
||||||
#endif
|
#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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
if (argc != 2)
|
if (argc != 2)
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
|
|
||||||
json_validator validator;
|
|
||||||
|
|
||||||
std::fstream f(argv[1]);
|
std::fstream f(argv[1]);
|
||||||
if (!f.good()) {
|
if (!f.good()) {
|
||||||
@ -69,41 +81,10 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2) insert this schema to the validator
|
// 2) insert this schema to the validator
|
||||||
// this resolves remote-schemas, sub-schemas and references
|
// this resolves remote-schemas, sub-schemas and references via the given loader-function
|
||||||
bool error = false;
|
json_validator validator(schema, loader);
|
||||||
do {
|
|
||||||
// inserting with json_uri("#") means this is the document's root-schema
|
|
||||||
auto missing_schemas = validator.insert_schema(schema, json_uri("#"));
|
|
||||||
|
|
||||||
// schema was inserted and all references have been fulfilled
|
// 3) do the actual validation of the document
|
||||||
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
|
|
||||||
json document;
|
json document;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -28,8 +28,6 @@
|
|||||||
|
|
||||||
#include <json.hpp>
|
#include <json.hpp>
|
||||||
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
// make yourself a home - welcome to nlohmann's namespace
|
// make yourself a home - welcome to nlohmann's namespace
|
||||||
namespace nlohmann
|
namespace nlohmann
|
||||||
{
|
{
|
||||||
@ -161,6 +159,7 @@ class json_validator
|
|||||||
{
|
{
|
||||||
std::vector<std::shared_ptr<json>> schema_store_;
|
std::vector<std::shared_ptr<json>> schema_store_;
|
||||||
std::shared_ptr<json> root_schema_;
|
std::shared_ptr<json> root_schema_;
|
||||||
|
std::function<void(const json_uri &, json &)> schema_loader_ = nullptr;
|
||||||
|
|
||||||
std::map<json_uri, const json *> schema_refs_;
|
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_array(json &instance, const json &schema_, const std::string &name);
|
||||||
void validate_object(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:
|
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);
|
void validate(json &instance);
|
||||||
|
|
||||||
|
|||||||
@ -271,43 +271,51 @@ namespace nlohmann
|
|||||||
namespace json_schema_draft4
|
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
|
// allocate create a copy for later storage - if resolving reference works
|
||||||
std::shared_ptr<json> schema = std::make_shared<json>(input);
|
std::shared_ptr<json> schema = std::make_shared<json>(input);
|
||||||
|
|
||||||
// resolve all local schemas and references
|
do {
|
||||||
resolver r(*schema, id);
|
// resolve all local schemas and references
|
||||||
|
resolver r(*schema, id);
|
||||||
|
|
||||||
// check whether all undefined schema references can be resolved with existing ones
|
// check whether all undefined schema references can be resolved with existing ones
|
||||||
std::set<json_uri> undefined;
|
std::set<json_uri> undefined;
|
||||||
for (auto &ref : r.undefined_refs)
|
for (auto &ref : r.undefined_refs)
|
||||||
if (schema_refs_.find(ref) == schema_refs_.end()) { // exact schema reference not found
|
if (schema_refs_.find(ref) == schema_refs_.end()) // exact schema reference not found
|
||||||
undefined.insert(ref);
|
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
|
if (schema_loader_ == nullptr)
|
||||||
// before retrying
|
throw std::invalid_argument("schema contains undefined references to other schemas, needed schema-loader.");
|
||||||
if (undefined.size() > 0)
|
|
||||||
return undefined;
|
|
||||||
|
|
||||||
// check whether all schema-references are new
|
for (auto undef : undefined) {
|
||||||
for (auto &sref : r.schema_refs) {
|
json ext;
|
||||||
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_loader_(undef, ext);
|
||||||
schema_store_.push_back(schema);
|
insert_schema(ext, undef.url());
|
||||||
|
}
|
||||||
// and insert all references
|
} while (1);
|
||||||
schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end());
|
|
||||||
|
|
||||||
// store the document root-schema
|
// store the document root-schema
|
||||||
if (id == json_uri("#"))
|
if (id == json_uri("#"))
|
||||||
root_schema_ = schema;
|
root_schema_ = schema;
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void json_validator::validate(json &instance)
|
void json_validator::validate(json &instance)
|
||||||
@ -318,10 +326,17 @@ void json_validator::validate(json &instance)
|
|||||||
validate(instance, *root_schema_, "root");
|
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)
|
void json_validator::validate(json &instance, const json &schema_, const std::string &name)
|
||||||
{
|
{
|
||||||
const json *schema = &schema_;
|
const json *schema = &schema_;
|
||||||
|
|
||||||
|
// $ref resolution
|
||||||
do {
|
do {
|
||||||
const auto &ref = schema->find("$ref");
|
const auto &ref = schema->find("$ref");
|
||||||
if (ref == schema->end())
|
if (ref == schema->end())
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user