Partially update the json validator
This commit is contained in:
parent
d8343a97dd
commit
cd886a19d3
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue