Fix #28: Differentiate integer and floating point validation

This commit is contained in:
Bryan Gillespie 2018-04-02 13:05:58 -04:00 committed by Patrick Boettcher
parent 7ee17659fe
commit 2db9c1e426
2 changed files with 84 additions and 34 deletions

View File

@ -34,6 +34,14 @@ lives in his namespace.
Schema-reference resolution is not recursivity-proven: If there is a nested Schema-reference resolution is not recursivity-proven: If there is a nested
cross-schema reference, it will not stop. (Though I haven't tested it) cross-schema reference, it will not stop. (Though I haven't tested it)
Numerical validation uses `int64_t`, `uint64_t` or `double`, depending on if
the schema type is "integer" or "number". Bignum (i.e. arbitrary precision and
range) is not supported at this time.
Unsigned integer validation will only take place if the following two conditions are true:
- The nlohmann `type()` of the json object under validation is `nlohmann::json::value_t::number_unsigned`
- The schema specifies a numerical minimum greater than or equal to 0
# How to use # How to use
The current state of the build-system needs at least version **3.1.1** of NLohmann's The current state of the build-system needs at least version **3.1.1** of NLohmann's

View File

@ -122,7 +122,7 @@ public:
for (auto r : refs) { for (auto r : refs) {
if (schema_refs.find(r) == schema_refs.end()) { if (schema_refs.find(r) == schema_refs.end()) {
if (r.url() == id.url()) // same url means referencing a sub-schema if (r.url() == id.url()) // same url means referencing a sub-schema
// of the same document, which has not been found // of the same document, which has not been found
throw std::invalid_argument("sub-schema " + r.pointer().to_string() + throw std::invalid_argument("sub-schema " + r.pointer().to_string() +
" in schema " + id.to_string() + " not found"); " in schema " + id.to_string() + " not found");
undefined_refs.insert(r.url()); undefined_refs.insert(r.url());
@ -186,64 +186,106 @@ void validate_boolean(const json & /*instance*/, const json &schema, const std::
validate_type(schema, "boolean", name); validate_type(schema, "boolean", name);
} }
void validate_numeric(const json &schema, const std::string &name, double value) template <class T>
bool violates_numeric_maximum(T max, T value, bool exclusive)
{ {
// multipleOf - if the rest of the division is 0 -> OK if (exclusive)
const auto &multipleOf = schema.find("multipleOf"); return value >= max;
if (multipleOf != schema.end()) {
if (multipleOf.value().get<double>() != 0.0) {
double v = value; return value > max;
v /= multipleOf.value().get<double>(); }
if (v != (double) (long) v) template <class T>
throw std::out_of_range(name + " is not a multiple ..."); bool violates_numeric_minimum(T min, T value, bool exclusive)
{
if (exclusive)
return value <= min;
return value < min;
}
// multipleOf - if the rest of the division is 0 -> OK
bool violates_multiple_of(json::number_float_t x, json::number_float_t y)
{
json::number_integer_t n = static_cast<json::number_integer_t>(x / y);
double res = (x - n * y);
return fabs(res) > std::numeric_limits<json::number_float_t>::epsilon();
}
template <class T>
void validate_numeric(const json &instance, const json &schema, const std::string &name)
{
T value = instance;
if (value != 0) { // zero is multiple of everything
const auto &multipleOf = schema.find("multipleOf");
if (multipleOf != schema.end()) {
double multiple = multipleOf.value();
double value_float = value;
if (violates_multiple_of(value_float, multiple))
throw std::out_of_range(name + " is not a multiple of " + std::to_string(multiple));
} }
} }
const auto &maximum = schema.find("maximum"); const auto &maximum = schema.find("maximum");
if (maximum != schema.end()) { if (maximum != schema.end()) {
double maxi = maximum.value(); T maxi = maximum.value();
auto ex = std::out_of_range(name + " exceeds maximum of " + std::to_string(maxi));
if (schema.find("exclusiveMaximum") != schema.end()) { const auto &excl = schema.find("exclusiveMaximum");
if (value >= maxi) bool exclusive = (excl != schema.end()) ? excl.value().get<bool>() : false;
throw ex;
} else { if (violates_numeric_maximum<T>(maxi, value, exclusive))
if (value > maxi) throw std::out_of_range(name + " exceeds maximum of " + std::to_string(maxi));
throw ex;
}
} }
const auto &minimum = schema.find("minimum"); const auto &minimum = schema.find("minimum");
if (minimum != schema.end()) { if (minimum != schema.end()) {
double mini = minimum.value(); T mini = minimum.value();
auto ex = std::out_of_range(name + " is below the minimum of " + std::to_string(mini));
if (schema.find("exclusiveMinimum") != schema.end()) { const auto &excl = schema.find("exclusiveMinimum");
if (value <= mini) bool exclusive = (excl != schema.end()) ? excl.value().get<bool>() : false;
throw ex;
} else { if (violates_numeric_minimum<T>(mini, value, exclusive))
if (value < mini) throw std::out_of_range(name + " is below minimum of " + std::to_string(mini));
throw ex;
}
} }
} }
void validate_integer(const json &instance, const json &schema, const std::string &name) void validate_integer(const json &instance, const json &schema, const std::string &name)
{ {
validate_type(schema, "integer", name); validate_type(schema, "integer", name);
validate_numeric(schema, name, instance.get<double>()); //TODO: Validate schema values are json::value_t::number_integer/unsigned?
validate_numeric<int64_t>(instance, schema, name);
}
bool is_unsigned(const json &schema)
{
const auto &minimum = schema.find("minimum");
// Number is expected to be unsigned if a minimum >= 0 is set
return minimum != schema.end() && minimum.value() >= 0;
} }
void validate_unsigned(const json &instance, const json &schema, const std::string &name) void validate_unsigned(const json &instance, const json &schema, const std::string &name)
{ {
validate_type(schema, "integer", name); validate_type(schema, "integer", name);
validate_numeric(schema, name, instance.get<double>()); //TODO: Validate schema values are json::value_t::unsigned?
//Is there a better way to determine whether an unsigned comparison should take place?
if (is_unsigned(schema))
validate_numeric<uint64_t>(instance, schema, name);
else
validate_numeric<int64_t>(instance, schema, name);
} }
void validate_float(const json &instance, const json &schema, const std::string &name) void validate_float(const json &instance, const json &schema, const std::string &name)
{ {
validate_type(schema, "number", name); validate_type(schema, "number", name);
validate_numeric(schema, name, instance.get<double>()); //TODO: Validate schema values are json::value_t::number_float?
validate_numeric<double>(instance, schema, name);
} }
void validate_null(const json & /*instance*/, const json &schema, const std::string &name) void validate_null(const json & /*instance*/, const json &schema, const std::string &name)
@ -504,7 +546,7 @@ void json_validator::validate_array(const json &instance, const json &schema, co
validate(value, items[i], sub_name); validate(value, items[i], sub_name);
else { else {
switch (additionalItems.type()) { // items is an array switch (additionalItems.type()) { // items is an array
// we need to take into consideration additionalItems // we need to take into consideration additionalItems
case json::value_t::object: case json::value_t::object:
validate(value, additionalItems, sub_name); validate(value, additionalItems, sub_name);
break; break;
@ -736,5 +778,5 @@ void json_validator::validate_string(const json &instance, const json &schema, c
format_check_(attr.value(), instance); format_check_(attr.value(), instance);
} }
} }
} } // namespace json_schema_draft4
} } // namespace nlohmann