Partially update the json validator

This commit is contained in:
Marek Roszko 2024-04-12 20:00:12 -04:00
parent d8343a97dd
commit cd886a19d3
3 changed files with 121 additions and 58 deletions

View File

@ -71,45 +71,45 @@ const nlohmann::json patch_schema = R"patch({
namespace nlohmann
{
json_patch::json_patch(json &&patch)
: j_(std::move(patch))
json_patch::json_patch( json&& patch ) : j_( std::move( patch ) )
{
validateJsonPatch(j_);
validateJsonPatch( j_ );
}
json_patch::json_patch(const json &patch)
: j_(std::move(patch))
json_patch::json_patch( const json& patch ) : j_( std::move( patch ) )
{
validateJsonPatch(j_);
validateJsonPatch( j_ );
}
json_patch &json_patch::add(const json::json_pointer &ptr, json value)
json_patch& json_patch::add( const json::json_pointer& ptr, json value )
{
j_.push_back(json{{"op", "add"}, {"path", ptr.to_string()}, {"value", std::move(value)}});
j_.push_back(
json{ { "op", "add" }, { "path", ptr.to_string() }, { "value", std::move( value ) } } );
return *this;
}
json_patch &json_patch::replace(const json::json_pointer &ptr, json value)
json_patch& json_patch::replace( const json::json_pointer& ptr, json value )
{
j_.push_back(json{{"op", "replace"}, {"path", ptr.to_string()}, {"value", std::move(value)}});
j_.push_back( json{
{ "op", "replace" }, { "path", ptr.to_string() }, { "value", std::move( value ) } } );
return *this;
}
json_patch &json_patch::remove(const json::json_pointer &ptr)
json_patch& json_patch::remove( const json::json_pointer& ptr )
{
j_.push_back(json{{"op", "remove"}, {"path", ptr.to_string()}});
j_.push_back( json{ { "op", "remove" }, { "path", ptr.to_string() } } );
return *this;
}
void json_patch::validateJsonPatch(json const &patch)
void json_patch::validateJsonPatch( json const& patch )
{
// static put here to have it created at the first usage of validateJsonPatch
static nlohmann::json_schema::json_validator patch_validator(patch_schema);
static nlohmann::json_schema::json_validator patch_validator( patch_schema );
patch_validator.validate(patch);
patch_validator.validate( patch );
for (auto const &op : patch)
json::json_pointer(op["path"].get<std::string>());
for( auto const& op : patch )
json::json_pointer( op["path"].get<std::string>() );
}
} // namespace nlohmann

View File

@ -8,10 +8,9 @@ namespace nlohmann
class JsonPatchFormatException : public std::exception
{
public:
explicit JsonPatchFormatException(std::string msg)
: ex_{std::move(msg)} {}
explicit JsonPatchFormatException( std::string msg ) : ex_{ std::move( msg ) } {}
inline const char *what() const noexcept override final { return ex_.c_str(); }
inline const char* what() const noexcept override final { return ex_.c_str(); }
private:
std::string ex_;
@ -21,18 +20,21 @@ class json_patch
{
public:
json_patch() = default;
json_patch(json &&patch);
json_patch(const json &patch);
json_patch( json&& patch );
json_patch( const json& patch );
json_patch &add(const json::json_pointer &, json value);
json_patch &replace(const json::json_pointer &, json value);
json_patch &remove(const json::json_pointer &);
json_patch& add( const json::json_pointer&, json value );
json_patch& replace( const json::json_pointer&, json value );
json_patch& remove( const json::json_pointer& );
json& get_json() { return j_; }
const json& get_json() const { return j_; }
operator json() const { return j_; }
private:
json j_;
json j_ = nlohmann::json::array();
static void validateJsonPatch(json const &patch);
static void validateJsonPatch( json const& patch );
};
} // namespace nlohmann

View File

@ -226,10 +226,11 @@ public:
// 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;
}
@ -425,6 +426,32 @@ 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
{
@ -433,26 +460,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 {
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,
@ -477,21 +511,24 @@ 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_);
{
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");
@ -553,6 +590,9 @@ class type_schema : public schema
else_->validate(ptr, instance, patch, e);
}
}
if (instance.is_null()) {
patch.add(nlohmann::json::json_pointer{}, default_value_);
}
}
protected:
@ -601,10 +641,12 @@ public:
} break;
case json::value_t::array: // "type": ["type1", "type2"]
for (auto &schema_type : attr.value())
for (auto &array_value : attr.value()) {
auto schema_type = array_value.get<std::string>();
for (auto &t : schema_types)
if (t.first == schema_type)
type_[static_cast<uint8_t>(t.second)] = type_schema::make(sch, t.second, root, uris, known_keywords);
}
break;
default:
@ -856,7 +898,12 @@ class numeric : public schema
bool violates_multiple_of(T x) const
{
double res = std::remainder(x, multipleOf_.second);
double multiple = std::fabs(x / multipleOf_.second);
if (multiple > 1) {
res = res / multiple;
}
double eps = std::nextafter(x, 0) - static_cast<double>(x);
return std::fabs(res) > std::fabs(eps);
}
@ -864,22 +911,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());
}
}
@ -1134,6 +1190,11 @@ public:
propertyNames_ = schema::make(attr.value(), root, {"propertyNames"}, uris);
sch.erase(attr);
}
attr = sch.find("default");
if (attr != sch.end()) {
set_default_value(*attr);
}
}
};