From 998f97ba4e1169d8dbf2b9fd689d74536d883782 Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Tue, 27 Dec 2016 11:39:48 +0100 Subject: [PATCH] validator: handle undefined schema-loader via a std::function given at construction-time --- app/json-schema-test.cpp | 78 +++++++++++++----------------------- app/json-schema-validate.cpp | 51 ++++++++--------------- src/json-schema.hpp | 7 ++-- src/json-validator.cpp | 63 ++++++++++++++++++----------- 4 files changed, 87 insertions(+), 112 deletions(-) diff --git a/app/json-schema-test.cpp b/app/json-schema-test.cpp index 7bb7ce9..cbbe21e 100644 --- a/app/json-schema-test.cpp +++ b/app/json-schema-test.cpp @@ -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 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 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 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"; } diff --git a/app/json-schema-validate.cpp b/app/json-schema-validate.cpp index c0b0260..92fb52b 100644 --- a/app/json-schema-validate.cpp +++ b/app/json-schema-validate.cpp @@ -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 { diff --git a/src/json-schema.hpp b/src/json-schema.hpp index 0d68a06..0daa25c 100644 --- a/src/json-schema.hpp +++ b/src/json-schema.hpp @@ -28,8 +28,6 @@ #include -#include - // make yourself a home - welcome to nlohmann's namespace namespace nlohmann { @@ -161,6 +159,7 @@ class json_validator { std::vector> schema_store_; std::shared_ptr root_schema_; + std::function schema_loader_ = nullptr; std::map 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 insert_schema(const json &input, json_uri id); + json_validator(const json &schema, std::function loader); void validate(json &instance); diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 1b2d05f..5db7f8e 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -271,43 +271,51 @@ namespace nlohmann namespace json_schema_draft4 { -std::set 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 schema = std::make_shared(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 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 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 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())