From 9dc77f7159599e471365bc222836656fa0dd53fc Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Mon, 21 Jan 2019 20:30:33 +0000 Subject: [PATCH 01/14] Avoid "warning C4457: declaration of 'e' hides function parameter" from Visual Studio and equivalent in GCC --- src/json-validator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 6903fe2..159f999 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -340,8 +340,8 @@ class type_schema : public schema if (enum_.first) { bool seen_in_enum = false; - for (auto &e : enum_.second) - if (instance == e) { + for (auto &v : enum_.second) + if (instance == v) { seen_in_enum = true; break; } From cf32c4e8fd4f2748274143d0fc59aecea88828cb Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Tue, 22 Jan 2019 12:43:15 +0000 Subject: [PATCH 02/14] [#47] Suppress "warning C4244: 'argument': conversion from '__int64' to 'double', possible loss of data" from Visual Studio --- src/json-validator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 159f999..80c23db 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -591,8 +591,8 @@ class numeric : public schema std::pair multipleOf_{false, 0}; - // multipleOf - if the rest of the division is 0 -> OK - bool violates_multiple_of(json::number_float_t x) const + // multipleOf - if the remainder of the division is 0 -> OK + bool violates_multiple_of(T x) const { json::number_integer_t n = static_cast(x / multipleOf_.second); double res = (x - n * multipleOf_.second); From a1c65315402ed67448e3cf85eb1630c18861a0e2 Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Tue, 22 Jan 2019 12:50:03 +0000 Subject: [PATCH 03/14] [#48] Tolerable difference depends on input values (and since x must be larger than multipleOf value in order to succeed, that's the critical one) --- src/json-validator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 80c23db..797ea77 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -594,9 +594,9 @@ class numeric : public schema // multipleOf - if the remainder of the division is 0 -> OK bool violates_multiple_of(T x) const { - json::number_integer_t n = static_cast(x / multipleOf_.second); - double res = (x - n * multipleOf_.second); - return fabs(res) > std::numeric_limits::epsilon(); + double res = std::remainder(x, multipleOf_.second); + double eps = std::nextafter(x, 0) - x; + return std::fabs(res) > std::fabs(eps); } void validate(const json &instance, basic_error_handler &e) const override From 011bd4470e4435beddb29a1a7f3621429305eca9 Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Tue, 22 Jan 2019 12:53:53 +0000 Subject: [PATCH 04/14] Performing runtime tests on the combine_logic template argument causes "warning C4127: conditional expression is constant" from Visual Studio. Refactor so that the appropriate tests are selected at compile time. --- src/json-validator.cpp | 66 +++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 797ea77..c876c8e 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -267,28 +267,21 @@ class logical_combination : public schema for (auto &s : subschemata_) { basic_error_handler err; s->validate(instance, err); - - if (err) { - //sub_schema_err << " one schema failed because: " << e.what() << "\n"; - if (combine_logic == allOf) { - e.error("", instance, "at least one schema has failed, but ALLOF them are required to validate."); - return; - } - } else + if (!err) count++; - if (combine_logic == oneOf && count > 1) { - e.error("", instance, "more than one schema has succeeded, but only ONEOF them is required to validate."); - return; - } - if (combine_logic == anyOf && count == 1) + if (is_validate_complete(instance, e, err, count)) return; } - if ((combine_logic == anyOf || combine_logic == oneOf) && count == 0) + if (count == 0) e.error("", instance, "no validation has succeeded but ANYOF/ONEOF them is required to validate."); } + // specialized for each of the logical_combination_types + static const std::string key; + static bool is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t count); + public: logical_combination(json &sch, root_schema *root, @@ -296,24 +289,43 @@ public: : schema(root) { size_t c = 0; - std::string key; - switch (combine_logic) { - case allOf: - key = "allOf"; - break; - case oneOf: - key = "oneOf"; - break; - case anyOf: - key = "anyOf"; - break; - } - for (auto &subschema : sch) subschemata_.push_back(schema::make(subschema, root, {key, std::to_string(c++)}, uris)); + + // value of allOf, anyOf, and oneOf "MUST be a non-empty array" + // TODO error/throw? when subschemata_.empty() } }; +template <> +const std::string logical_combination::key = "allOf"; +template <> +const std::string logical_combination::key = "anyOf"; +template <> +const std::string logical_combination::key = "oneOf"; + +template <> +bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t count) +{ + if (err) + e.error("", instance, "at least one schema has failed, but ALLOF them are required to validate."); + return err; +} + +template <> +bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t count) +{ + return count == 1; +} + +template <> +bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t count) +{ + if (count > 1) + e.error("", instance, "more than one schema has succeeded, but only ONEOF them is required to validate."); + return count > 1; +} + class type_schema : public schema { std::vector> type_; From 4b6330a0a833cc288a2f74a09a7b28adbec8d3a1 Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Thu, 24 Jan 2019 18:25:06 +0100 Subject: [PATCH 05/14] fix format and remove unused arg-warning --- src/json-schema.hpp | 4 ++-- src/json-validator.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/json-schema.hpp b/src/json-schema.hpp index 5eaf31d..853282a 100644 --- a/src/json-schema.hpp +++ b/src/json-schema.hpp @@ -139,9 +139,9 @@ class JSON_SCHEMA_VALIDATOR_API json_validator public: json_validator(std::function loader = nullptr, std::function format = nullptr); - json_validator(json_validator&&); + json_validator(json_validator &&); ~json_validator(); - json_validator& operator=(json_validator&&); + json_validator &operator=(json_validator &&); // insert and set thea root-schema void set_root_schema(const json &); diff --git a/src/json-validator.cpp b/src/json-validator.cpp index c876c8e..96a439d 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -305,7 +305,7 @@ template <> const std::string logical_combination::key = "oneOf"; template <> -bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t count) +bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t) { if (err) e.error("", instance, "at least one schema has failed, but ALLOF them are required to validate."); @@ -313,13 +313,13 @@ bool logical_combination::is_validate_complete(const json &instance, basi } template <> -bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t count) +bool logical_combination::is_validate_complete(const json &, basic_error_handler &, bool, size_t count) { return count == 1; } template <> -bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t count) +bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool, size_t count) { if (count > 1) e.error("", instance, "more than one schema has succeeded, but only ONEOF them is required to validate."); @@ -1101,9 +1101,9 @@ json_validator::json_validator(std::function loa // move constructor, destructor and move assignment operator can be defaulted here // where root_schema is a complete type -json_validator::json_validator(json_validator&&) = default; +json_validator::json_validator(json_validator &&) = default; json_validator::~json_validator() = default; -json_validator& json_validator::operator=(json_validator&&) = default; +json_validator &json_validator::operator=(json_validator &&) = default; void json_validator::set_root_schema(const json &schema) { From e8a9f66b1dcbe722baa75a0592d6a1b547e6f223 Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Thu, 24 Jan 2019 18:27:19 +0100 Subject: [PATCH 06/14] add non-regression test for #48 --- test/issue-48/CMakeLists.txt | 3 +++ test/issue-48/instance.json | 1 + test/issue-48/schema.json | 3 +++ 3 files changed, 7 insertions(+) create mode 100644 test/issue-48/CMakeLists.txt create mode 100644 test/issue-48/instance.json create mode 100644 test/issue-48/schema.json diff --git a/test/issue-48/CMakeLists.txt b/test/issue-48/CMakeLists.txt new file mode 100644 index 0000000..832d374 --- /dev/null +++ b/test/issue-48/CMakeLists.txt @@ -0,0 +1,3 @@ +add_test_simple_schema(Issue::48 + ${CMAKE_CURRENT_SOURCE_DIR}/schema.json + ${CMAKE_CURRENT_SOURCE_DIR}/instance.json) diff --git a/test/issue-48/instance.json b/test/issue-48/instance.json new file mode 100644 index 0000000..5625e59 --- /dev/null +++ b/test/issue-48/instance.json @@ -0,0 +1 @@ +1.2 diff --git a/test/issue-48/schema.json b/test/issue-48/schema.json new file mode 100644 index 0000000..71782fc --- /dev/null +++ b/test/issue-48/schema.json @@ -0,0 +1,3 @@ +{ + "multipleOf": 0.1 +} From 746394922a6b0e595fa8e17341a3fde075cf118e Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Thu, 24 Jan 2019 12:25:47 +0100 Subject: [PATCH 07/14] Use push_back of nlohmann::json_pointer if available. --- README.md | 4 +--- src/json-schema.hpp | 5 +++++ src/json-validator.cpp | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 691af12..65c5692 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,6 @@ is parsed into compiled C++ objects which are then used during validation. There still optimizations to be done, but validation speed has improved by factor 100 or more. -In JSON-schema one sub-schema can be - # Design goals The main goal of this validator is to produce *human-comprehensible* error @@ -44,7 +42,7 @@ messages if a JSON-document/instance does not comply to its schema. By default this is done with exceptions thrown at the users with a helpful message telling what's wrong with the document while validating. -With **2.0.0** the user can passed a `json_scheam::basic_error_handler` derived object +With **2.0.0** the user can pass a `json_scheam::basic_error_handler` derived object along with the instance to validate to receive a each time a validation error occurs and decice what to do (throwing, counting, collecting). diff --git a/src/json-schema.hpp b/src/json-schema.hpp index 853282a..22c15f1 100644 --- a/src/json-schema.hpp +++ b/src/json-schema.hpp @@ -92,7 +92,12 @@ public: json_uri append(const std::string &field) const { json_uri u = *this; +#if NLOHMANN_JSON_VERSION_MAJOR >= 3 && NLOHMANN_JSON_VERSION_MINOR >= 5 && NLOHMANN_JSON_VERSION_PATCH >= 1 + u.pointer_.push_back(field); +#else u.pointer_ = nlohmann::json::json_pointer(u.pointer_.to_string() + '/' + escape(field)); +#endif + return u; } diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 96a439d..4ca00d8 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -1023,7 +1023,7 @@ std::shared_ptr schema::make(json &schema, // append to all URIs the keys for this sub-schema for (auto &key : keys) for (auto &uri : uris) - uri = uri.append(json_uri::escape(key)); + uri = uri.append(key); std::shared_ptr<::schema> sch; From 6c482e10355c650b5840fecdb30f05b98d8441c9 Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Thu, 24 Jan 2019 16:52:00 +0100 Subject: [PATCH 08/14] error-handler now receives a json_pointer as path Indicating where in the instance the error occurred. The pointer is relative to the root of the instance. --- app/json-schema-validate.cpp | 13 +--- app/readme.cpp | 6 +- src/json-schema.hpp | 21 ++--- src/json-uri.cpp | 2 +- src/json-validator.cpp | 147 ++++++++++++++++++----------------- test/CMakeLists.txt | 3 + test/errors.cpp | 117 ++++++++++++++++++++++++++++ 7 files changed, 211 insertions(+), 98 deletions(-) create mode 100644 test/errors.cpp diff --git a/app/json-schema-validate.cpp b/app/json-schema-validate.cpp index b6699e3..e0061d2 100644 --- a/app/json-schema-validate.cpp +++ b/app/json-schema-validate.cpp @@ -21,13 +21,6 @@ static void usage(const char *name) exit(EXIT_FAILURE); } -#if 0 - resolver r(nlohmann::json_schema_draft4::root_schema, - nlohmann::json_schema_draft4::root_schema["id"]); - schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end()); - assert(r.undefined_refs.size() == 0); -#endif - static void loader(const json_uri &uri, json &schema) { std::string filename = "./" + uri.path(); @@ -44,10 +37,10 @@ static void loader(const json_uri &uri, json &schema) class custom_error_handler : public nlohmann::json_schema::basic_error_handler { - void error(const std::string &path, const json &instance, const std::string &message) override + void error(const nlohmann::json::json_pointer &ptr, const json &instance, const std::string &message) override { - nlohmann::json_schema::basic_error_handler::error(path, instance, message); - std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n"; + nlohmann::json_schema::basic_error_handler::error(ptr, instance, message); + std::cerr << "ERROR: '" << ptr << "' - '" << instance << "': " << message << "\n"; } }; diff --git a/app/readme.cpp b/app/readme.cpp index a25d652..9122f48 100644 --- a/app/readme.cpp +++ b/app/readme.cpp @@ -65,10 +65,10 @@ int main() /* json-parse the people - with custom error handler */ class custom_error_handler : public nlohmann::json_schema::basic_error_handler { - void error(const std::string &path, const json &instance, const std::string &message) override + void error(const nlohmann::json::json_pointer &ptr, const json &instance, const std::string &message) override { - nlohmann::json_schema::basic_error_handler::error(path, instance, message); - std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n"; + nlohmann::json_schema::basic_error_handler::error(ptr, instance, message); + std::cerr << "ERROR: '" << ptr << "' - '" << instance << "': " << message << "\n"; } }; diff --git a/src/json-schema.hpp b/src/json-schema.hpp index 22c15f1..3f2fff7 100644 --- a/src/json-schema.hpp +++ b/src/json-schema.hpp @@ -24,11 +24,11 @@ #include #ifdef NLOHMANN_JSON_VERSION_MAJOR -# if NLOHMANN_JSON_VERSION_MAJOR < 3 || NLOHMANN_JSON_VERSION_MINOR < 5 || NLOHMANN_JSON_VERSION_PATCH < 0 -# error "Please use this library with NLohmann's JSON version 3.5.0 or higher" +# if (NLOHMANN_JSON_VERSION_MAJOR * 10000 + NLOHMANN_JSON_VERSION_MINOR * 100 + NLOHMANN_JSON_VERSION_PATCH) < 30600 +# error "Please use this library with NLohmann's JSON version 3.6.0 or higher" # endif #else -# error "expected existing NLOHMANN_JSON_VERSION_MAJOR preproc variable, please update to NLohmann's JSON 3.5.0" +# error "expected existing NLOHMANN_JSON_VERSION_MAJOR preproc variable, please update to NLohmann's JSON 3.6.0" #endif // make yourself a home - welcome to nlohmann's namespace @@ -51,7 +51,7 @@ class JSON_SCHEMA_VALIDATOR_API json_uri std::string proto_; std::string hostname_; std::string path_; - nlohmann::json::json_pointer pointer_; + json::json_pointer pointer_; protected: // decodes a JSON uri and replaces all or part of the currently stored values @@ -72,7 +72,7 @@ public: const std::string hostname() const { return hostname_; } const std::string path() const { return path_; } - const nlohmann::json::json_pointer pointer() const { return pointer_; } + const json::json_pointer pointer() const { return pointer_; } const std::string url() const { return location(); } const std::string location() const; @@ -92,12 +92,7 @@ public: json_uri append(const std::string &field) const { json_uri u = *this; -#if NLOHMANN_JSON_VERSION_MAJOR >= 3 && NLOHMANN_JSON_VERSION_MINOR >= 5 && NLOHMANN_JSON_VERSION_PATCH >= 1 - u.pointer_.push_back(field); -#else - u.pointer_ = nlohmann::json::json_pointer(u.pointer_.to_string() + '/' + escape(field)); -#endif - + u.pointer_ /= field; return u; } @@ -126,12 +121,12 @@ class basic_error_handler bool error_{false}; public: - virtual void error(const std::string & /*path*/, const json & /* instance */, const std::string & /*message*/) + virtual void error(const json::json_pointer & /*path*/, const json & /* instance */, const std::string & /*message*/) { error_ = true; } - void reset() { error_ = false; } + virtual void reset() { error_ = false; } operator bool() const { return error_; } }; diff --git a/src/json-uri.cpp b/src/json-uri.cpp index f8d6cbe..c0e37cb 100644 --- a/src/json-uri.cpp +++ b/src/json-uri.cpp @@ -91,7 +91,7 @@ void json_uri::update(const std::string &uri) } } - pointer_ = nlohmann::json::json_pointer(pointer); + pointer_ = json::json_pointer(pointer); } const std::string json_uri::location() const diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 4ca00d8..03e98fd 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -39,7 +39,7 @@ public: schema(root_schema *root) : root_(root) {} - virtual void validate(const json &instance, basic_error_handler &e) const = 0; + virtual void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const = 0; static std::shared_ptr make(json &schema, root_schema *root, @@ -52,12 +52,12 @@ class schema_ref : public schema const std::string id_; std::shared_ptr target_; - void validate(const json &instance, basic_error_handler &e) const final + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final { if (target_) - target_->validate(instance, e); + target_->validate(ptr, instance, e); else - e.error("", instance, "unresolved schema-reference " + id_); + e.error(ptr, instance, "unresolved schema-reference " + id_); } public: @@ -83,8 +83,8 @@ class root_schema : public schema std::shared_ptr root_; struct schema_file { - std::map> schemas; - std::map> unresolved; // contains all unresolved references from any other file seen during parsing + std::map> schemas; + std::map> unresolved; // contains all unresolved references from any other file seen during parsing json unknown_keywords; }; @@ -211,12 +211,12 @@ public: } while (1); } - void validate(const json &instance, basic_error_handler &e) const final + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final { if (root_) - root_->validate(instance, e); + root_->validate(ptr, instance, e); else - e.error("", "", "no root schema has yet been set for validating an instance."); + e.error(ptr, "", "no root schema has yet been set for validating an instance."); } }; @@ -230,13 +230,13 @@ class logical_not : public schema { std::shared_ptr subschema_; - void validate(const json &instance, basic_error_handler &e) const final + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final { basic_error_handler err; - subschema_->validate(instance, err); + subschema_->validate(ptr, instance, err); if (!err) - e.error("", instance, "instance is valid, whereas it should NOT be as required by schema"); + e.error(ptr, instance, "instance is valid, whereas it should NOT be as required by schema"); } public: @@ -260,27 +260,28 @@ class logical_combination : public schema { std::vector> subschemata_; - void validate(const json &instance, basic_error_handler &e) const final + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final { size_t count = 0; for (auto &s : subschemata_) { basic_error_handler err; - s->validate(instance, err); + s->validate(ptr, instance, err); + if (!err) count++; - if (is_validate_complete(instance, e, err, count)) + if (is_validate_complete(instance, ptr, e, err, count)) return; } if (count == 0) - e.error("", instance, "no validation has succeeded but ANYOF/ONEOF them is required to validate."); + e.error(ptr, instance, "no validation has succeeded but ANYOF/ONEOF them is required to validate."); } // specialized for each of the logical_combination_types static const std::string key; - static bool is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t count); + static bool is_validate_complete(const json &, const json::json_pointer &, basic_error_handler &, bool, size_t); public: logical_combination(json &sch, @@ -305,24 +306,24 @@ template <> const std::string logical_combination::key = "oneOf"; template <> -bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool err, size_t) +bool logical_combination::is_validate_complete(const json &instance, const json::json_pointer &ptr, basic_error_handler &e, bool err, size_t) { if (err) - e.error("", instance, "at least one schema has failed, but ALLOF them are required to validate."); + e.error(ptr, instance, "at least one schema has failed, but ALLOF them are required to validate."); return err; } template <> -bool logical_combination::is_validate_complete(const json &, basic_error_handler &, bool, size_t count) +bool logical_combination::is_validate_complete(const json &, const json::json_pointer &, basic_error_handler &, bool, size_t count) { return count == 1; } template <> -bool logical_combination::is_validate_complete(const json &instance, basic_error_handler &e, bool, size_t count) +bool logical_combination::is_validate_complete(const json &instance, const json::json_pointer &ptr, basic_error_handler &e, bool, size_t count) { if (count > 1) - e.error("", instance, "more than one schema has succeeded, but only ONEOF them is required to validate."); + e.error(ptr, instance, "more than one schema has succeeded, but only ONEOF them is required to validate."); return count > 1; } @@ -340,15 +341,15 @@ class type_schema : public schema std::shared_ptr if_, then_, else_; - void validate(const json &instance, basic_error_handler &e) const override final + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override final { // depending on the type of instance run the type specific validator - if present auto type = type_[(uint8_t) instance.type()]; if (type) - type->validate(instance, e); + type->validate(ptr, instance, e); else - e.error("", instance, "unexpected instance type"); + e.error(ptr, instance, "unexpected instance type"); if (enum_.first) { bool seen_in_enum = false; @@ -359,26 +360,26 @@ class type_schema : public schema } if (!seen_in_enum) - e.error("", instance, "instance not found in required enum"); + e.error(ptr, instance, "instance not found in required enum"); } if (const_.first && const_.second != instance) - e.error("", instance, "instance not const"); + e.error(ptr, instance, "instance not const"); for (auto l : logic_) - l->validate(instance, e); + l->validate(ptr, instance, e); if (if_) { basic_error_handler err; - if_->validate(instance, err); + if_->validate(ptr, instance, err); if (!err) { if (then_) - then_->validate(instance, e); + then_->validate(ptr, instance, e); } else { if (else_) - else_->validate(instance, e); + else_->validate(ptr, instance, e); } } } @@ -521,13 +522,13 @@ class string : public schema return len; } - void validate(const json &instance, basic_error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override { if (minLength_.first) { if (utf8_length(instance) < minLength_.second) { std::ostringstream s; s << "'" << instance << "' is too short as per minLength (" << minLength_.second << ")"; - e.error("", instance, s.str()); + e.error(ptr, instance, s.str()); } } @@ -535,24 +536,24 @@ class string : public schema if (utf8_length(instance) > maxLength_.second) { std::ostringstream s; s << "'" << instance << "' is too long as per maxLength (" << maxLength_.second << ")"; - e.error("", instance, s.str()); + e.error(ptr, instance, s.str()); } } #ifndef NO_STD_REGEX if (pattern_.first && !REGEX_NAMESPACE::regex_search(instance.get(), pattern_.second)) - e.error("", instance, instance.get() + " does not match regex pattern: " + patternString_); + e.error(ptr, instance, instance.get() + " does not match regex pattern: " + patternString_); #endif if (format_.first) { if (root_->format_check() == nullptr) - e.error("", instance, std::string("A format checker was not provided but a format-attribute for this string is present. ") + " cannot be validated for " + format_.second); + e.error(ptr, instance, std::string("A format checker was not provided but a format-attribute for this string is present. ") + " cannot be validated for " + format_.second); else { try { root_->format_check()(format_.second, instance); } catch (const std::exception &ex) { - e.error("", instance, std::string("Format-checking failed: ") + ex.what()); + e.error(ptr, instance, std::string("Format-checking failed: ") + ex.what()); } } } @@ -611,23 +612,23 @@ class numeric : public schema return std::fabs(res) > std::fabs(eps); } - void validate(const json &instance, basic_error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override { T value = instance; // conversion of json to value_type if (multipleOf_.first && value != 0) // zero is multiple of everything if (violates_multiple_of(value)) - e.error("", instance, "is not a multiple of " + std::to_string(multipleOf_.second)); + e.error(ptr, instance, "is not a multiple of " + std::to_string(multipleOf_.second)); if (maximum_.first) if ((exclusiveMaximum_ && value >= maximum_.second) || value > maximum_.second) - e.error("", instance, "exceeds maximum of " + std::to_string(maximum_.second)); + e.error(ptr, instance, "exceeds maximum of " + std::to_string(maximum_.second)); if (minimum_.first) if ((exclusiveMinimum_ && value <= minimum_.second) || value < minimum_.second) - e.error("", instance, "is below minimum of " + std::to_string(minimum_.second)); + e.error(ptr, instance, "is below minimum of " + std::to_string(minimum_.second)); } public: @@ -670,10 +671,10 @@ public: class null : public schema { - void validate(const json &instance, basic_error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override { if (!instance.is_null()) - e.error("", instance, "expected to be null"); + e.error(ptr, instance, "expected to be null"); } public: @@ -683,7 +684,7 @@ public: class boolean_type : public schema { - void validate(const json &, basic_error_handler &) const override {} + void validate(const json::json_pointer &, const json &, basic_error_handler &) const override {} public: boolean_type(json &, root_schema *root) @@ -693,18 +694,18 @@ public: class boolean : public schema { bool true_; - void validate(const json &instance, basic_error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override { if (!true_) { // false schema // empty array //switch (instance.type()) { //case json::value_t::array: // if (instance.size() != 0) // valid false-schema - // e.error("", instance, "false-schema required empty array"); + // e.error(ptr, instance, "false-schema required empty array"); // return; //} - e.error("", instance, "instance invalid as par false-schema"); + e.error(ptr, instance, "instance invalid as par false-schema"); } } @@ -717,11 +718,11 @@ class required : public schema { const std::vector required_; - void validate(const json &instance, basic_error_handler &e) const override final + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override final { for (auto &r : required_) if (instance.find(r) == instance.end()) - e.error("", instance, "required property '" + r + "' not found in object as a dependency"); + e.error(ptr, instance, "required property '" + r + "' not found in object as a dependency"); } public: @@ -745,46 +746,46 @@ class object : public schema std::shared_ptr propertyNames_; - void validate(const json &instance, basic_error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override { if (maxProperties_.first && instance.size() > maxProperties_.second) - e.error("", instance, "too many properties."); + e.error(ptr, instance, "too many properties."); if (minProperties_.first && instance.size() < minProperties_.second) - e.error("", instance, "too few properties."); + e.error(ptr, instance, "too few properties."); for (auto &r : required_) if (instance.find(r) == instance.end()) - e.error("", instance, "required property '" + r + "' not found in object '"); + e.error(ptr, instance, "required property '" + r + "' not found in object"); // for each property in instance for (auto &p : instance.items()) { if (propertyNames_) - propertyNames_->validate(p.key(), e); + propertyNames_->validate(ptr, p.key(), e); bool a_prop_or_pattern_matched = false; auto schema_p = properties_.find(p.key()); // check if it is in "properties" if (schema_p != properties_.end()) { a_prop_or_pattern_matched = true; - schema_p->second->validate(p.value(), e); + schema_p->second->validate(ptr / p.key(), p.value(), e); } // check all matching patternProperties for (auto &schema_pp : patternProperties_) if (REGEX_NAMESPACE::regex_search(p.key(), schema_pp.first)) { a_prop_or_pattern_matched = true; - schema_pp.second->validate(p.value(), e); + schema_pp.second->validate(ptr / p.key(), p.value(), e); } // check additionalProperties as a last resort if (!a_prop_or_pattern_matched && additionalProperties_) - additionalProperties_->validate(p.value(), e); + additionalProperties_->validate(ptr / p.key(), p.value(), e); } for (auto &dep : dependencies_) { auto prop = instance.find(dep.first); - if (prop != instance.end()) // if dependency-property is present in instance - dep.second->validate(instance, e); // validate + if (prop != instance.end()) // if dependency-property is present in instance + dep.second->validate(ptr / dep.first, instance, e); // validate } } @@ -879,25 +880,28 @@ class array : public schema std::shared_ptr contains_; - void validate(const json &instance, basic_error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override { if (maxItems_.first && instance.size() > maxItems_.second) - e.error("", instance, "has too many items."); + e.error(ptr, instance, "has too many items."); if (minItems_.first && instance.size() < minItems_.second) - e.error("", instance, "has too few items."); + e.error(ptr, instance, "has too few items."); if (uniqueItems_) { for (auto it = instance.cbegin(); it != instance.cend(); ++it) { auto v = std::find(it + 1, instance.end(), *it); if (v != instance.end()) - e.error("", instance, "items have to be unique for this array."); + e.error(ptr, instance, "items have to be unique for this array."); } } + size_t index = 0; if (items_schema_) - for (auto &i : instance) - items_schema_->validate(i, e); + for (auto &i : instance) { + items_schema_->validate(ptr / index, i, e); + index++; + } else { auto item = items_.cbegin(); for (auto &i : instance) { @@ -912,7 +916,7 @@ class array : public schema if (!item_validator) break; - item_validator->validate(i, e); + item_validator->validate(ptr / index, i, e); } } @@ -920,14 +924,14 @@ class array : public schema bool contained = false; for (auto &item : instance) { basic_error_handler local_e; - contains_->validate(item, local_e); + contains_->validate(ptr, item, local_e); if (!local_e) { contained = true; break; } } if (!contained) - e.error("", instance, "array does not contain required element as per 'contains'"); + e.error(ptr, instance, "array does not contain required element as per 'contains'"); } } @@ -1080,9 +1084,9 @@ std::shared_ptr schema::make(json &schema, class throwing_error_handler : public basic_error_handler { - void error(const std::string &path, const json &instance, const std::string &message) + void error(const json::json_pointer &ptr, const json &instance, const std::string &message) { - throw std::invalid_argument(std::string("At ") + path + " of " + instance.dump() + " - " + message + "\n"); + throw std::invalid_argument(std::string("At ") + ptr.to_string() + " of " + instance.dump() + " - " + message + "\n"); } }; @@ -1118,7 +1122,8 @@ void json_validator::validate(const json &instance) void json_validator::validate(const json &instance, basic_error_handler &err) { - root_->validate(instance, err); + json::json_pointer ptr; + root_->validate(ptr, instance, err); } } // namespace json_schema diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b1b1c01..5d08e41 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,4 +22,7 @@ endforeach() add_executable(uri uri.cpp) target_link_libraries(uri json-schema-validator) +add_executable(errors errors.cpp) +target_link_libraries(errors json-schema-validator) + add_test(NAME uri COMMAND uri) diff --git a/test/errors.cpp b/test/errors.cpp new file mode 100644 index 0000000..dfa6b88 --- /dev/null +++ b/test/errors.cpp @@ -0,0 +1,117 @@ +#include + +#include + +static int error_count; + +#define EXPECT_EQ(a, b) \ + do { \ + if (a != b) { \ + std::cerr << "Failed: '" << a << "' != '" << b << "'\n"; \ + error_count++; \ + } \ + } while (0) + +using nlohmann::json; +using nlohmann::json_schema::json_validator; + +namespace +{ + +// The schema is defined based upon a string literal +static json person_schema = R"( +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "A person", + "properties": { + "name": { + "description": "Name", + "type": "string" + }, + "age": { + "description": "Age of the person", + "type": "number", + "minimum": 2, + "maximum": 200 + }, + "phones": { + "type": "array", + "items": { + "type": "number" + } + } + }, + "required": [ + "name", + "age" + ], + "type": "object" +})"_json; + +class store_ptr_err_handler : public nlohmann::json_schema::basic_error_handler +{ + void error(const nlohmann::json::json_pointer &ptr, const json &instance, const std::string &message) override + { + nlohmann::json_schema::basic_error_handler::error(ptr, instance, message); + std::cerr << "ERROR: '" << ptr << "' - '" << instance << "': " << message << "\n"; + failed_pointers.push_back(ptr); + } + +public: + std::vector failed_pointers; + + void reset() override + { + nlohmann::json_schema::basic_error_handler::reset(); + failed_pointers.clear(); + } +}; + +} // namespace + +int main(void) +{ + json_validator validator; + + try { + validator.set_root_schema(person_schema); // insert root-schema + } catch (const std::exception &e) { + std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n"; + return EXIT_FAILURE; + } + + store_ptr_err_handler err; + + validator.validate({{"age", 42}, {"name", "John"}}, err); // OK + EXPECT_EQ(err.failed_pointers.size(), 0); + err.reset(); + + validator.validate({{"age", 42}}, err); // no name + + EXPECT_EQ(err.failed_pointers[0].to_string(), ""); + EXPECT_EQ(err.failed_pointers.size(), 1); + err.reset(); + + validator.validate({{"street", "Boulevard"}}, err); // no name and no age + EXPECT_EQ(err.failed_pointers[0].to_string(), ""); + EXPECT_EQ(err.failed_pointers[1].to_string(), ""); + EXPECT_EQ(err.failed_pointers.size(), 2); + err.reset(); + + validator.validate({{"age", 42}, {"name", 12}}, err); // name must be a string + EXPECT_EQ(err.failed_pointers[0].to_string(), "/name"); + EXPECT_EQ(err.failed_pointers.size(), 1); + err.reset(); + + validator.validate({ + {"age", 42}, + {"name", "John"}, + {"phones", {1234, "223"}}, + }, + err); // name must be a string + EXPECT_EQ(err.failed_pointers[0].to_string(), "/phones/1"); + EXPECT_EQ(err.failed_pointers.size(), 1); + err.reset(); + + return error_count; +} From c40fb7aa34df8dd0872b0d8866c919563de5fcb0 Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Wed, 20 Mar 2019 15:44:14 +0100 Subject: [PATCH 09/14] Improve error-message-grammar and style. --- src/json-validator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 03e98fd..3b60bfc 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -276,7 +276,7 @@ class logical_combination : public schema } if (count == 0) - e.error(ptr, instance, "no validation has succeeded but ANYOF/ONEOF them is required to validate."); + e.error(ptr, instance, "no validation has succeeded but one of them is required to validate."); } // specialized for each of the logical_combination_types @@ -309,7 +309,7 @@ template <> bool logical_combination::is_validate_complete(const json &instance, const json::json_pointer &ptr, basic_error_handler &e, bool err, size_t) { if (err) - e.error(ptr, instance, "at least one schema has failed, but ALLOF them are required to validate."); + e.error(ptr, instance, "at least one schema has failed, but all of them are required to validate."); return err; } @@ -323,7 +323,7 @@ template <> bool logical_combination::is_validate_complete(const json &instance, const json::json_pointer &ptr, basic_error_handler &e, bool, size_t count) { if (count > 1) - e.error(ptr, instance, "more than one schema has succeeded, but only ONEOF them is required to validate."); + e.error(ptr, instance, "more than one schema has succeeded, but exactly one of them is required to validate."); return count > 1; } @@ -705,7 +705,7 @@ class boolean : public schema // return; //} - e.error(ptr, instance, "instance invalid as par false-schema"); + e.error(ptr, instance, "instance invalid as per false-schema"); } } From b05248426e9cf9da24157f3dceb903e077404379 Mon Sep 17 00:00:00 2001 From: garethsb-sony Date: Tue, 5 Feb 2019 13:51:45 +0000 Subject: [PATCH 10/14] fix comments --- app/readme.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/readme.cpp b/app/readme.cpp index 9122f48..227a560 100644 --- a/app/readme.cpp +++ b/app/readme.cpp @@ -55,7 +55,7 @@ int main() std::cout << "About to validate this person:\n" << std::setw(2) << person << std::endl; try { - validator.validate(person); // validate the document + validator.validate(person); // validate the document - uses the default throwing error-handler std::cout << "Validation succeeded\n"; } catch (const std::exception &e) { std::cerr << "Validation failed, here is why: " << e.what() << "\n"; @@ -78,7 +78,7 @@ int main() << std::setw(2) << person << std::endl; custom_error_handler err; - validator.validate(person, err); // validate the document - uses the default throwing error-handler + validator.validate(person, err); // validate the document if (err) std::cerr << "Validation failed\n"; From 0b1fb66b217fed16c5b7081ac47ca5bf1495b0b1 Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Wed, 20 Mar 2019 16:28:06 +0100 Subject: [PATCH 11/14] travis: use JSON 3.6.0 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index edb2981..9bd8a92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,7 @@ script: - $CXX --version # put json.hpp to nlohmann - - mkdir -p nlohmann && wget https://github.com/nlohmann/json/releases/download/v3.5.0/json.hpp -O nlohmann/json.hpp + - mkdir -p nlohmann && wget https://github.com/nlohmann/json/releases/download/v3.6.0/json.hpp -O nlohmann/json.hpp # compile and execute unit tests - mkdir -p build && cd build From abfa3852f5980cc285fd54d7efc08cbf4f49f0ff Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Wed, 20 Mar 2019 16:36:59 +0100 Subject: [PATCH 12/14] README updates and fixes --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 65c5692..02026b8 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This is a C++ library for validating JSON documents based on a [draft-7 of JSON Schema Validation](http://json-schema.org/schema). First a disclaimer: *It is work in progress and -contributions or hints or discussions are welcome.* Even though a 2.0.0 release is immenent. +contributions or hints or discussions are welcome.* Even though a 2.0.0 release is imminent. Niels Lohmann et al develop a great JSON parser for C++ called [JSON for Modern C++](https://github.com/nlohmann/json). This validator is based on this @@ -21,7 +21,7 @@ is rather simple. # New in version 2 -Although significant changes have been coorporate to the 2 version +Although significant changes have been done for the 2nd version (a complete rewrite) the API is compatible with the 1.0.0 release. Except for the namespace which is now `nlohmann::json_schema. @@ -29,7 +29,7 @@ Version **2** supports JSON schema draft 7, whereas 1 was supporting draft 4 only. Please update your schemas. The primary change in 2 is the way a schema is used. While in version 1 the schema was -kept as a JSON-document and used again and again during validation, in versin 2 the schema +kept as a JSON-document and used again and again during validation, in version 2 the schema is parsed into compiled C++ objects which are then used during validation. There are surely still optimizations to be done, but validation speed has improved by factor 100 or more. @@ -42,9 +42,9 @@ messages if a JSON-document/instance does not comply to its schema. By default this is done with exceptions thrown at the users with a helpful message telling what's wrong with the document while validating. -With **2.0.0** the user can pass a `json_scheam::basic_error_handler` derived object -along with the instance to validate to receive a each time a validation error occurs -and decice what to do (throwing, counting, collecting). +With **2.0.0** the user can pass a `json_scheam::basic_error_handler`-derived +object along with the instance to validate to receive a callback each time a +validation error occurs and decide what to do (throwing, counting, collecting). Another goal was to use Niels Lohmann's JSON-library. This is why the validator lives in his namespace. @@ -55,12 +55,12 @@ Numerical validation uses nlohmann integer, unsigned and floating point types, d the schema type is "integer" or "number". Bignum (i.e. arbitrary precision and range) is not supported at this time. -Currently JSON-URI with "plain name fragments" are not supported. So referring to an URI +Currently JSON-URI with "plain name fragments" are not supported: referring to an URI with `$ref: "file.json#plain"` will not work. # How to use -The current state of the build-system needs at least version **3.5.0** of NLohmann's +The current state of the build-system needs at least version **3.6.0** of NLohmann's JSON library. It is looking for the `json.hpp` within a `nlohmann/`-path. When build the library you need to provide the path to the directory where the include-file @@ -167,7 +167,7 @@ int main() std::cout << "About to validate this person:\n" << std::setw(2) << person << std::endl; try { - validator.validate(person); // validate the document + validator.validate(person); // validate the document - uses the default throwing error-handler std::cout << "Validation succeeded\n"; } catch (const std::exception &e) { std::cerr << "Validation failed, here is why: " << e.what() << "\n"; @@ -190,7 +190,7 @@ int main() << std::setw(2) << person << std::endl; custom_error_handler err; - validator.validate(person, err); // validate the document - uses the default throwing error-handler + validator.validate(person, err); // validate the document if (err) std::cerr << "Validation failed\n"; From b01dcdb9841c80725a706d3c460979f59afcf676 Mon Sep 17 00:00:00 2001 From: vpasacek <45885156+vpasacek@users.noreply.github.com> Date: Fri, 14 Dec 2018 23:56:08 +0100 Subject: [PATCH 13/14] Install also dynamic library (dll) on windows --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b6afc34..95fef2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,8 @@ set_target_properties(json-schema-validator install(TARGETS json-schema-validator LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib) + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin) install(DIRECTORY src/ DESTINATION include From d6ed73f240742943942ea56f5e20ba5be646f721 Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Wed, 20 Mar 2019 16:58:03 +0100 Subject: [PATCH 14/14] Fix #53: only add -Wall/-Wextra for GCC and Clang --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95fef2b..c209683 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,12 +60,13 @@ target_include_directories(json-schema-validator target_compile_features(json-schema-validator PUBLIC cxx_range_for) # for C++11 - flags -# Enable more compiler warnings, except when using Visual Studio compiler -if(NOT MSVC) + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") target_compile_options(json-schema-validator PRIVATE -Wall -Wextra) endif() + target_link_libraries(json-schema-validator PUBLIC json-hpp)