diff --git a/NOTICES b/NOTICES index 15a206d8..8d6f4cb8 100644 --- a/NOTICES +++ b/NOTICES @@ -24,7 +24,7 @@ SOFTWARE. ******************************************************************************* -Modern C++ JSON schema validator - 2.3.0 +Modern C++ JSON schema validator - 2.4.0 Modern C++ JSON schema validator is licensed under the MIT License : diff --git a/externals/json-schema-validator/json-validator.cpp b/externals/json-schema-validator/json-validator.cpp index 1fd0de1a..62ce97ce 100644 --- a/externals/json-schema-validator/json-validator.cpp +++ b/externals/json-schema-validator/json-validator.cpp @@ -226,10 +226,11 @@ class root_schema // for each token create an object, if not already existing auto unk_kw = &file.unknown_keywords; for (auto &rt : ref_tokens) { - auto existing_object = unk_kw->find(rt); - if (existing_object == unk_kw->end()) + // create a json_pointer from rt as rt can be an stringified integer doing find on an array won't work + json::json_pointer rt_ptr{"/" + rt}; + if (unk_kw->contains(rt_ptr) == false) (*unk_kw)[rt] = json::object(); - unk_kw = &(*unk_kw)[rt]; + unk_kw = &(*unk_kw)[rt_ptr]; } (*unk_kw)[key] = value; } @@ -253,15 +254,15 @@ class root_schema // // an unknown keyword can only be referenced by a json-pointer, // not by a plain name fragment - if (uri.pointer().to_string() != "") { - try { - auto &subschema = file.unknown_keywords.at(uri.pointer()); // null is returned if not existing - auto s = schema::make(subschema, this, {}, {{uri}}); // A JSON Schema MUST be an object or a boolean. - if (s) { // nullptr if invalid schema, e.g. null + if (!uri.pointer().to_string().empty()) { + bool contains_pointer = file.unknown_keywords.contains(uri.pointer()); + if (contains_pointer) { + auto &subschema = file.unknown_keywords.at(uri.pointer()); + auto s = schema::make(subschema, this, {}, {{uri}}); + if (s) { // if schema is valid (non-null) file.unknown_keywords.erase(uri.fragment()); return s; } - } catch (nlohmann::detail::out_of_range &) { // at() did not find it } } @@ -425,6 +426,31 @@ enum logical_combination_types { oneOf }; +class logical_combination_error_handler : public error_handler +{ +public: + struct error_entry { + json::json_pointer ptr_; + json instance_; + std::string message_; + }; + + std::vector error_entry_list_; + + void error(const json::json_pointer &ptr, const json &instance, const std::string &message) override + { + error_entry_list_.push_back(error_entry{ptr, instance, message}); + } + + void propagate(error_handler &e, const std::string &prefix) const + { + for (const error_entry &entry : error_entry_list_) + e.error(entry.ptr_, entry.instance_, prefix + entry.message_); + } + + operator bool() const { return !error_entry_list_.empty(); } +}; + template class logical_combination : public schema { @@ -433,29 +459,33 @@ class logical_combination : public schema void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const final { size_t count = 0; + logical_combination_error_handler error_summary; - for (auto &s : subschemata_) { - first_error_handler esub; + for (std::size_t index = 0; index < subschemata_.size(); ++index) { + const std::shared_ptr &s = subschemata_[index]; + logical_combination_error_handler esub; auto oldPatchSize = patch.get_json().size(); s->validate(ptr, instance, patch, esub); if (!esub) count++; - else + else { patch.get_json().get_ref().resize(oldPatchSize); + esub.propagate(error_summary, "case#" + std::to_string(index) + "] "); + } - if (is_validate_complete(instance, ptr, e, esub, count)) + if (is_validate_complete(instance, ptr, e, esub, count, index)) return; } - // could accumulate esub details for anyOf and oneOf, but not clear how to select which subschema failure to report - // or how to report multiple such failures - if (count == 0) - e.error(ptr, instance, "no subschema has succeeded, but one of them is required to validate"); + if (count == 0) { + e.error(ptr, instance, "no subschema has succeeded, but one of them is required to validate. Type: " + key + ", number of failed subschemas: " + std::to_string(subschemata_.size())); + error_summary.propagate(e, "[combination: " + key + " / "); + } } // specialized for each of the logical_combination_types static const std::string key; - static bool is_validate_complete(const json &, const json::json_pointer &, error_handler &, const first_error_handler &, size_t); + static bool is_validate_complete(const json &, const json::json_pointer &, error_handler &, const logical_combination_error_handler &, size_t, size_t); public: logical_combination(json &sch, @@ -480,21 +510,23 @@ template <> const std::string logical_combination::key = "oneOf"; template <> -bool logical_combination::is_validate_complete(const json &, const json::json_pointer &, error_handler &e, const first_error_handler &esub, size_t) +bool logical_combination::is_validate_complete(const json &, const json::json_pointer &, error_handler &e, const logical_combination_error_handler &esub, size_t, size_t current_schema_index) { - if (esub) - e.error(esub.ptr_, esub.instance_, "at least one subschema has failed, but all of them are required to validate - " + esub.message_); + if (esub) { + e.error(esub.error_entry_list_.front().ptr_, esub.error_entry_list_.front().instance_, "at least one subschema has failed, but all of them are required to validate - " + esub.error_entry_list_.front().message_); + esub.propagate(e, "[combination: allOf / case#" + std::to_string(current_schema_index) + "] "); + } return esub; } template <> -bool logical_combination::is_validate_complete(const json &, const json::json_pointer &, error_handler &, const first_error_handler &, size_t count) +bool logical_combination::is_validate_complete(const json &, const json::json_pointer &, error_handler &, const logical_combination_error_handler &, size_t count, size_t) { return count == 1; } template <> -bool logical_combination::is_validate_complete(const json &instance, const json::json_pointer &ptr, error_handler &e, const first_error_handler &, size_t count) +bool logical_combination::is_validate_complete(const json &instance, const json::json_pointer &ptr, error_handler &e, const logical_combination_error_handler &, size_t count, size_t) { if (count > 1) e.error(ptr, instance, "more than one subschema has succeeded, but exactly one of them is required to validate"); @@ -877,22 +909,31 @@ class numeric : public schema { T value = instance; // conversion of json to value_type + std::ostringstream oss; + if (multipleOf_.first && value != 0) // zero is multiple of everything if (violates_multiple_of(value)) - e.error(ptr, instance, "instance is not a multiple of " + std::to_string(multipleOf_.second)); + oss << "instance is not a multiple of " << json(multipleOf_.second); if (maximum_.first) { if (exclusiveMaximum_ && value >= maximum_.second) - e.error(ptr, instance, "instance exceeds or equals maximum of " + std::to_string(maximum_.second)); + oss << "instance exceeds or equals maximum of " << json(maximum_.second); else if (value > maximum_.second) - e.error(ptr, instance, "instance exceeds maximum of " + std::to_string(maximum_.second)); + oss << "instance exceeds maximum of " << json(maximum_.second); } if (minimum_.first) { if (exclusiveMinimum_ && value <= minimum_.second) - e.error(ptr, instance, "instance is below or equals minimum of " + std::to_string(minimum_.second)); + oss << "instance is below or equals minimum of " << json(minimum_.second); else if (value < minimum_.second) - e.error(ptr, instance, "instance is below minimum of " + std::to_string(minimum_.second)); + oss << "instance is below minimum of " << json(minimum_.second); + } + + oss.seekp(0, std::ios::end); + auto size = oss.tellp(); + if (size != 0) { + oss.seekp(0, std::ios::beg); + e.error(ptr, instance, oss.str()); } } @@ -1345,11 +1386,18 @@ std::shared_ptr schema::make(json &schema, schema.erase(attr); } - attr = schema.find("definitions"); - if (attr != schema.end()) { - for (auto &def : attr.value().items()) - schema::make(def.value(), root, {"definitions", def.key()}, uris); - schema.erase(attr); + auto findDefinitions = [&](const std::string &defs) -> bool { + attr = schema.find(defs); + if (attr != schema.end()) { + for (auto &def : attr.value().items()) + schema::make(def.value(), root, {defs, def.key()}, uris); + schema.erase(attr); + return true; + } + return false; + }; + if (!findDefinitions("$defs")) { + findDefinitions("definitions"); } attr = schema.find("$ref"); diff --git a/externals/json-schema-validator/smtp-address-validator.cpp b/externals/json-schema-validator/smtp-address-validator.cpp index 5ddd8110..3b4a4612 100644 --- a/externals/json-schema-validator/smtp-address-validator.cpp +++ b/externals/json-schema-validator/smtp-address-validator.cpp @@ -687,7 +687,7 @@ bool is_address(const char *p, const char *pe) const signed char *_keys; const signed char *_acts; unsigned int _nacts; - _resume : { + _resume: { } if (p == pe && p != eof) goto _out; @@ -746,7 +746,7 @@ bool is_address(const char *p, const char *pe) } } - _match : { + _match: { } } cs = (int) _address_cond_targs[_trans]; @@ -785,7 +785,7 @@ bool is_address(const char *p, const char *pe) goto _resume; } } - _out : { + _out: { } } return result;