From 2db9c1e42637c121180c72ee38b1d9ae6c9214a0 Mon Sep 17 00:00:00 2001 From: Bryan Gillespie Date: Mon, 2 Apr 2018 13:05:58 -0400 Subject: [PATCH] Fix #28: Differentiate integer and floating point validation --- README.md | 8 +++ src/json-validator.cpp | 110 ++++++++++++++++++++++++++++------------- 2 files changed, 84 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index ac55838..adb5c77 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,14 @@ lives in his namespace. 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) +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 The current state of the build-system needs at least version **3.1.1** of NLohmann's diff --git a/src/json-validator.cpp b/src/json-validator.cpp index ecb88da..5bfc809 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -122,7 +122,7 @@ public: for (auto r : refs) { if (schema_refs.find(r) == schema_refs.end()) { 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() + " in schema " + id.to_string() + " not found"); 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); } -void validate_numeric(const json &schema, const std::string &name, double value) +template +bool violates_numeric_maximum(T max, T value, bool exclusive) { - // multipleOf - if the rest of the division is 0 -> OK - const auto &multipleOf = schema.find("multipleOf"); - if (multipleOf != schema.end()) { - if (multipleOf.value().get() != 0.0) { + if (exclusive) + return value >= max; - double v = value; - v /= multipleOf.value().get(); + return value > max; +} - if (v != (double) (long) v) - throw std::out_of_range(name + " is not a multiple ..."); +template +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(x / y); + double res = (x - n * y); + return fabs(res) > std::numeric_limits::epsilon(); +} + +template +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"); if (maximum != schema.end()) { - double maxi = maximum.value(); - auto ex = std::out_of_range(name + " exceeds maximum of " + std::to_string(maxi)); - if (schema.find("exclusiveMaximum") != schema.end()) { - if (value >= maxi) - throw ex; - } else { - if (value > maxi) - throw ex; - } + T maxi = maximum.value(); + + const auto &excl = schema.find("exclusiveMaximum"); + bool exclusive = (excl != schema.end()) ? excl.value().get() : false; + + if (violates_numeric_maximum(maxi, value, exclusive)) + throw std::out_of_range(name + " exceeds maximum of " + std::to_string(maxi)); } const auto &minimum = schema.find("minimum"); if (minimum != schema.end()) { - double 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()) { - if (value <= mini) - throw ex; - } else { - if (value < mini) - throw ex; - } + T mini = minimum.value(); + + const auto &excl = schema.find("exclusiveMinimum"); + bool exclusive = (excl != schema.end()) ? excl.value().get() : false; + + if (violates_numeric_minimum(mini, value, exclusive)) + throw std::out_of_range(name + " is below minimum of " + std::to_string(mini)); } } void validate_integer(const json &instance, const json &schema, const std::string &name) { validate_type(schema, "integer", name); - validate_numeric(schema, name, instance.get()); + //TODO: Validate schema values are json::value_t::number_integer/unsigned? + + validate_numeric(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) { validate_type(schema, "integer", name); - validate_numeric(schema, name, instance.get()); + //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(instance, schema, name); + else + validate_numeric(instance, schema, name); } void validate_float(const json &instance, const json &schema, const std::string &name) { validate_type(schema, "number", name); - validate_numeric(schema, name, instance.get()); + //TODO: Validate schema values are json::value_t::number_float? + + validate_numeric(instance, schema, 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); else { 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: validate(value, additionalItems, sub_name); break; @@ -736,5 +778,5 @@ void json_validator::validate_string(const json &instance, const json &schema, c format_check_(attr.value(), instance); } } -} -} +} // namespace json_schema_draft4 +} // namespace nlohmann