Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -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
<http://opensource.org/licenses/MIT>:
Expand Down
114 changes: 81 additions & 33 deletions externals/json-schema-validator/json-validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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> 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 <enum logical_combination_types combine_logic>
class logical_combination : public schema
{
Expand All @@ -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<schema> &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<nlohmann::json::array_t &>().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,
Expand All @@ -480,21 +510,23 @@ template <>
const std::string logical_combination<oneOf>::key = "oneOf";

template <>
bool logical_combination<allOf>::is_validate_complete(const json &, const json::json_pointer &, error_handler &e, const first_error_handler &esub, size_t)
bool logical_combination<allOf>::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<anyOf>::is_validate_complete(const json &, const json::json_pointer &, error_handler &, const first_error_handler &, size_t count)
bool logical_combination<anyOf>::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<oneOf>::is_validate_complete(const json &instance, const json::json_pointer &ptr, error_handler &e, const first_error_handler &, size_t count)
bool logical_combination<oneOf>::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");
Expand Down Expand Up @@ -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());
}
}

Expand Down Expand Up @@ -1345,11 +1386,18 @@ std::shared_ptr<schema> 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");
Expand Down
6 changes: 3 additions & 3 deletions externals/json-schema-validator/smtp-address-validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -746,7 +746,7 @@ bool is_address(const char *p, const char *pe)
}
}

_match : {
_match: {
}
}
cs = (int) _address_cond_targs[_trans];
Expand Down Expand Up @@ -785,7 +785,7 @@ bool is_address(const char *p, const char *pe)
goto _resume;
}
}
_out : {
_out: {
}
}
return result;
Expand Down