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
|
namespace nlohmann
|
||||||
{
|
{
|
||||||
|
|
||||||
json_patch::json_patch(json &&patch)
|
json_patch::json_patch( json&& patch ) : j_( std::move( patch ) )
|
||||||
: j_(std::move(patch))
|
|
||||||
{
|
{
|
||||||
validateJsonPatch(j_);
|
validateJsonPatch( j_ );
|
||||||
}
|
}
|
||||||
|
|
||||||
json_patch::json_patch(const json &patch)
|
json_patch::json_patch( const json& patch ) : j_( std::move( 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(
|
||||||
return *this;
|
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{
|
||||||
return *this;
|
{ "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;
|
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 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)
|
for( auto const& op : patch )
|
||||||
json::json_pointer(op["path"].get<std::string>());
|
json::json_pointer( op["path"].get<std::string>() );
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace nlohmann
|
} // namespace nlohmann
|
|
@ -8,31 +8,33 @@ namespace nlohmann
|
||||||
class JsonPatchFormatException : public std::exception
|
class JsonPatchFormatException : public std::exception
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit JsonPatchFormatException(std::string msg)
|
explicit JsonPatchFormatException( std::string msg ) : ex_{ std::move( 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:
|
private:
|
||||||
std::string ex_;
|
std::string ex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class json_patch
|
class json_patch
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
json_patch() = default;
|
json_patch() = default;
|
||||||
json_patch(json &&patch);
|
json_patch( json&& patch );
|
||||||
json_patch(const json &patch);
|
json_patch( const json& patch );
|
||||||
|
|
||||||
json_patch &add(const json::json_pointer &, json value);
|
json_patch& add( const json::json_pointer&, json value );
|
||||||
json_patch &replace(const json::json_pointer &, json value);
|
json_patch& replace( const json::json_pointer&, json value );
|
||||||
json_patch &remove(const json::json_pointer &);
|
json_patch& remove( const json::json_pointer& );
|
||||||
|
|
||||||
operator json() const { return j_; }
|
json& get_json() { return j_; }
|
||||||
|
const json& get_json() const { return j_; }
|
||||||
|
|
||||||
|
operator json() const { return j_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
json j_;
|
json j_ = nlohmann::json::array();
|
||||||
|
|
||||||
static void validateJsonPatch(json const &patch);
|
static void validateJsonPatch( json const& patch );
|
||||||
};
|
};
|
||||||
} // namespace nlohmann
|
} // namespace nlohmann
|
|
@ -226,10 +226,11 @@ public:
|
||||||
// for each token create an object, if not already existing
|
// for each token create an object, if not already existing
|
||||||
auto unk_kw = &file.unknown_keywords;
|
auto unk_kw = &file.unknown_keywords;
|
||||||
for (auto &rt : ref_tokens) {
|
for (auto &rt : ref_tokens) {
|
||||||
auto existing_object = unk_kw->find(rt);
|
// create a json_pointer from rt as rt can be an stringified integer doing find on an array won't work
|
||||||
if (existing_object == unk_kw->end())
|
json::json_pointer rt_ptr{"/" + rt};
|
||||||
|
if (unk_kw->contains(rt_ptr) == false)
|
||||||
(*unk_kw)[rt] = json::object();
|
(*unk_kw)[rt] = json::object();
|
||||||
unk_kw = &(*unk_kw)[rt];
|
unk_kw = &(*unk_kw)[rt_ptr];
|
||||||
}
|
}
|
||||||
(*unk_kw)[key] = value;
|
(*unk_kw)[key] = value;
|
||||||
}
|
}
|
||||||
|
@ -425,6 +426,32 @@ enum logical_combination_types {
|
||||||
oneOf
|
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>
|
template <enum logical_combination_types combine_logic>
|
||||||
class logical_combination : public schema
|
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
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const final
|
||||||
{
|
{
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
|
logical_combination_error_handler error_summary;
|
||||||
|
|
||||||
for (auto &s : subschemata_) {
|
for (std::size_t index = 0; index < subschemata_.size(); ++index) {
|
||||||
first_error_handler esub;
|
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);
|
s->validate(ptr, instance, patch, esub);
|
||||||
if (!esub)
|
if (!esub)
|
||||||
count++;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// could accumulate esub details for anyOf and oneOf, but not clear how to select which subschema failure to report
|
if (count == 0) {
|
||||||
// or how to report multiple such failures
|
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()));
|
||||||
if (count == 0)
|
error_summary.propagate(e, "[combination: " + key + " / ");
|
||||||
e.error(ptr, instance, "no subschema has succeeded, but one of them is required to validate");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// specialized for each of the logical_combination_types
|
// specialized for each of the logical_combination_types
|
||||||
static const std::string key;
|
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:
|
public:
|
||||||
logical_combination(json &sch,
|
logical_combination(json &sch,
|
||||||
|
@ -477,21 +511,24 @@ template <>
|
||||||
const std::string logical_combination<oneOf>::key = "oneOf";
|
const std::string logical_combination<oneOf>::key = "oneOf";
|
||||||
|
|
||||||
template <>
|
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)
|
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;
|
return esub;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
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;
|
return count == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
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)
|
if (count > 1)
|
||||||
e.error(ptr, instance, "more than one subschema has succeeded, but exactly one of them is required to validate");
|
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);
|
else_->validate(ptr, instance, patch, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (instance.is_null()) {
|
||||||
|
patch.add(nlohmann::json::json_pointer{}, default_value_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -601,10 +641,12 @@ public:
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case json::value_t::array: // "type": ["type1", "type2"]
|
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)
|
for (auto &t : schema_types)
|
||||||
if (t.first == schema_type)
|
if (t.first == schema_type)
|
||||||
type_[static_cast<uint8_t>(t.second)] = type_schema::make(sch, t.second, root, uris, known_keywords);
|
type_[static_cast<uint8_t>(t.second)] = type_schema::make(sch, t.second, root, uris, known_keywords);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -856,7 +898,12 @@ class numeric : public schema
|
||||||
bool violates_multiple_of(T x) const
|
bool violates_multiple_of(T x) const
|
||||||
{
|
{
|
||||||
double res = std::remainder(x, multipleOf_.second);
|
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);
|
double eps = std::nextafter(x, 0) - static_cast<double>(x);
|
||||||
|
|
||||||
return std::fabs(res) > std::fabs(eps);
|
return std::fabs(res) > std::fabs(eps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -864,22 +911,31 @@ class numeric : public schema
|
||||||
{
|
{
|
||||||
T value = instance; // conversion of json to value_type
|
T value = instance; // conversion of json to value_type
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
|
||||||
if (multipleOf_.first && value != 0) // zero is multiple of everything
|
if (multipleOf_.first && value != 0) // zero is multiple of everything
|
||||||
if (violates_multiple_of(value))
|
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 (maximum_.first) {
|
||||||
if (exclusiveMaximum_ && value >= maximum_.second)
|
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)
|
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 (minimum_.first) {
|
||||||
if (exclusiveMinimum_ && value <= minimum_.second)
|
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)
|
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);
|
propertyNames_ = schema::make(attr.value(), root, {"propertyNames"}, uris);
|
||||||
sch.erase(attr);
|
sch.erase(attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attr = sch.find("default");
|
||||||
|
if (attr != sch.end()) {
|
||||||
|
set_default_value(*attr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1451,4 +1512,4 @@ json json_validator::validate(const json &instance, error_handler &err, const js
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace json_schema
|
} // namespace json_schema
|
||||||
} // namespace nlohmann
|
} // namespace nlohmann
|
Loading…
Reference in New Issue