From 24768e316de4c1ebf517eb8124b893aff6713d32 Mon Sep 17 00:00:00 2001
From: Patrick Boettcher
Date: Thu, 24 Jan 2019 16:52:00 +0100
Subject: [PATCH] error-handler now receives a json_pointer as path
Indicating where in the instance the error occurred.
The pointer is relative to the root of the instance.
---
app/json-schema-validate.cpp | 13 +---
app/readme.cpp | 6 +-
src/json-schema.hpp | 19 ++---
src/json-validator.cpp | 136 ++++++++++++++++++-----------------
test/CMakeLists.txt | 3 +
test/errors.cpp | 117 ++++++++++++++++++++++++++++++
6 files changed, 203 insertions(+), 91 deletions(-)
create mode 100644 test/errors.cpp
diff --git a/app/json-schema-validate.cpp b/app/json-schema-validate.cpp
index b6699e3..e0061d2 100644
--- a/app/json-schema-validate.cpp
+++ b/app/json-schema-validate.cpp
@@ -21,13 +21,6 @@ static void usage(const char *name)
exit(EXIT_FAILURE);
}
-#if 0
- resolver r(nlohmann::json_schema_draft4::root_schema,
- nlohmann::json_schema_draft4::root_schema["id"]);
- schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end());
- assert(r.undefined_refs.size() == 0);
-#endif
-
static void loader(const json_uri &uri, json &schema)
{
std::string filename = "./" + uri.path();
@@ -44,10 +37,10 @@ static void loader(const json_uri &uri, json &schema)
class custom_error_handler : public nlohmann::json_schema::basic_error_handler
{
- void error(const std::string &path, const json &instance, const std::string &message) override
+ void error(const nlohmann::json::json_pointer &ptr, const json &instance, const std::string &message) override
{
- nlohmann::json_schema::basic_error_handler::error(path, instance, message);
- std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n";
+ nlohmann::json_schema::basic_error_handler::error(ptr, instance, message);
+ std::cerr << "ERROR: '" << ptr << "' - '" << instance << "': " << message << "\n";
}
};
diff --git a/app/readme.cpp b/app/readme.cpp
index a25d652..9122f48 100644
--- a/app/readme.cpp
+++ b/app/readme.cpp
@@ -65,10 +65,10 @@ int main()
/* json-parse the people - with custom error handler */
class custom_error_handler : public nlohmann::json_schema::basic_error_handler
{
- void error(const std::string &path, const json &instance, const std::string &message) override
+ void error(const nlohmann::json::json_pointer &ptr, const json &instance, const std::string &message) override
{
- nlohmann::json_schema::basic_error_handler::error(path, instance, message);
- std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n";
+ nlohmann::json_schema::basic_error_handler::error(ptr, instance, message);
+ std::cerr << "ERROR: '" << ptr << "' - '" << instance << "': " << message << "\n";
}
};
diff --git a/src/json-schema.hpp b/src/json-schema.hpp
index 5d70646..e0a1410 100644
--- a/src/json-schema.hpp
+++ b/src/json-schema.hpp
@@ -24,11 +24,11 @@
#include
#ifdef NLOHMANN_JSON_VERSION_MAJOR
-# if NLOHMANN_JSON_VERSION_MAJOR < 3 || NLOHMANN_JSON_VERSION_MINOR < 5 || NLOHMANN_JSON_VERSION_PATCH < 0
-# error "Please use this library with NLohmann's JSON version 3.5.0 or higher"
+# if NLOHMANN_JSON_VERSION_MAJOR < 3 || NLOHMANN_JSON_VERSION_MINOR < 5 || NLOHMANN_JSON_VERSION_PATCH < 1
+# error "Please use this library with NLohmann's JSON version 3.5.1 or higher"
# endif
#else
-# error "expected existing NLOHMANN_JSON_VERSION_MAJOR preproc variable, please update to NLohmann's JSON 3.5.0"
+# error "expected existing NLOHMANN_JSON_VERSION_MAJOR preproc variable, please update to NLohmann's JSON 3.5.1"
#endif
// make yourself a home - welcome to nlohmann's namespace
@@ -92,12 +92,7 @@ public:
json_uri append(const std::string &field) const
{
json_uri u = *this;
-#if NLOHMANN_JSON_VERSION_MAJOR >= 3 && NLOHMANN_JSON_VERSION_MINOR >= 5 && NLOHMANN_JSON_VERSION_PATCH >= 1
u.pointer_.push_back(field);
-#else
- u.pointer_ = nlohmann::json::json_pointer(u.pointer_.to_string() + '/' + escape(field));
-#endif
-
return u;
}
@@ -126,12 +121,12 @@ class basic_error_handler
bool error_{false};
public:
- virtual void error(const std::string & /*path*/, const json & /* instance */, const std::string & /*message*/)
+ virtual void error(const nlohmann::json::json_pointer & /*path*/, const json & /* instance */, const std::string & /*message*/)
{
error_ = true;
}
- void reset() { error_ = false; }
+ virtual void reset() { error_ = false; }
operator bool() const { return error_; }
};
@@ -144,9 +139,9 @@ class JSON_SCHEMA_VALIDATOR_API json_validator
public:
json_validator(std::function loader = nullptr,
std::function format = nullptr);
- json_validator(json_validator&&);
+ json_validator(json_validator &&);
~json_validator();
- json_validator& operator=(json_validator&&);
+ json_validator &operator=(json_validator &&);
// insert and set thea root-schema
void set_root_schema(const json &);
diff --git a/src/json-validator.cpp b/src/json-validator.cpp
index fe36ee9..f80ce02 100644
--- a/src/json-validator.cpp
+++ b/src/json-validator.cpp
@@ -39,7 +39,7 @@ public:
schema(root_schema *root)
: root_(root) {}
- virtual void validate(const json &instance, basic_error_handler &e) const = 0;
+ virtual void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const = 0;
static std::shared_ptr make(json &schema,
root_schema *root,
@@ -52,12 +52,12 @@ class schema_ref : public schema
const std::string id_;
std::shared_ptr target_;
- void validate(const json &instance, basic_error_handler &e) const final
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
{
if (target_)
- target_->validate(instance, e);
+ target_->validate(ptr, instance, e);
else
- e.error("", instance, "unresolved schema-reference " + id_);
+ e.error(ptr, instance, "unresolved schema-reference " + id_);
}
public:
@@ -211,12 +211,12 @@ public:
} while (1);
}
- void validate(const json &instance, basic_error_handler &e) const final
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
{
if (root_)
- root_->validate(instance, e);
+ root_->validate(ptr, instance, e);
else
- e.error("", "", "no root schema has yet been set for validating an instance.");
+ e.error(ptr, "", "no root schema has yet been set for validating an instance.");
}
};
@@ -230,13 +230,13 @@ class logical_not : public schema
{
std::shared_ptr subschema_;
- void validate(const json &instance, basic_error_handler &e) const final
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
{
basic_error_handler err;
- subschema_->validate(instance, err);
+ subschema_->validate(ptr, instance, err);
if (!err)
- e.error("", instance, "instance is valid, whereas it should NOT be as required by schema");
+ e.error(ptr, instance, "instance is valid, whereas it should NOT be as required by schema");
}
public:
@@ -260,25 +260,25 @@ class logical_combination : public schema
{
std::vector> subschemata_;
- void validate(const json &instance, basic_error_handler &e) const final
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
{
size_t count = 0;
for (auto &s : subschemata_) {
basic_error_handler err;
- s->validate(instance, err);
+ s->validate(ptr, instance, err);
if (err) {
//sub_schema_err << " one schema failed because: " << e.what() << "\n";
if (combine_logic == allOf) {
- e.error("", instance, "at least one schema has failed, but ALLOF them are required to validate.");
+ e.error(ptr, instance, "at least one schema has failed, but ALLOF them are required to validate.");
return;
}
} else
count++;
if (combine_logic == oneOf && count > 1) {
- e.error("", instance, "more than one schema has succeeded, but only ONEOF them is required to validate.");
+ e.error(ptr, instance, "more than one schema has succeeded, but only ONEOF them is required to validate.");
return;
}
if (combine_logic == anyOf && count == 1)
@@ -286,7 +286,7 @@ class logical_combination : public schema
}
if ((combine_logic == anyOf || combine_logic == oneOf) && count == 0)
- e.error("", instance, "no validation has succeeded but ANYOF/ONEOF them is required to validate.");
+ e.error(ptr, instance, "no validation has succeeded but ANYOF/ONEOF them is required to validate.");
}
public:
@@ -328,15 +328,15 @@ class type_schema : public schema
std::shared_ptr if_, then_, else_;
- void validate(const json &instance, basic_error_handler &e) const override final
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override final
{
// depending on the type of instance run the type specific validator - if present
auto type = type_[(uint8_t) instance.type()];
if (type)
- type->validate(instance, e);
+ type->validate(ptr, instance, e);
else
- e.error("", instance, "unexpected instance type");
+ e.error(ptr, instance, "unexpected instance type");
if (enum_.first) {
bool seen_in_enum = false;
@@ -347,26 +347,26 @@ class type_schema : public schema
}
if (!seen_in_enum)
- e.error("", instance, "instance not found in required enum");
+ e.error(ptr, instance, "instance not found in required enum");
}
if (const_.first &&
const_.second != instance)
- e.error("", instance, "instance not const");
+ e.error(ptr, instance, "instance not const");
for (auto l : logic_)
- l->validate(instance, e);
+ l->validate(ptr, instance, e);
if (if_) {
basic_error_handler err;
- if_->validate(instance, err);
+ if_->validate(ptr, instance, err);
if (!err) {
if (then_)
- then_->validate(instance, e);
+ then_->validate(ptr, instance, e);
} else {
if (else_)
- else_->validate(instance, e);
+ else_->validate(ptr, instance, e);
}
}
}
@@ -509,13 +509,13 @@ class string : public schema
return len;
}
- void validate(const json &instance, basic_error_handler &e) const override
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
{
if (minLength_.first) {
if (utf8_length(instance) < minLength_.second) {
std::ostringstream s;
s << "'" << instance << "' is too short as per minLength (" << minLength_.second << ")";
- e.error("", instance, s.str());
+ e.error(ptr, instance, s.str());
}
}
@@ -523,24 +523,24 @@ class string : public schema
if (utf8_length(instance) > maxLength_.second) {
std::ostringstream s;
s << "'" << instance << "' is too long as per maxLength (" << maxLength_.second << ")";
- e.error("", instance, s.str());
+ e.error(ptr, instance, s.str());
}
}
#ifndef NO_STD_REGEX
if (pattern_.first &&
!REGEX_NAMESPACE::regex_search(instance.get(), pattern_.second))
- e.error("", instance, instance.get() + " does not match regex pattern: " + patternString_);
+ e.error(ptr, instance, instance.get() + " does not match regex pattern: " + patternString_);
#endif
if (format_.first) {
if (root_->format_check() == nullptr)
- e.error("", instance, std::string("A format checker was not provided but a format-attribute for this string is present. ") + " cannot be validated for " + format_.second);
+ e.error(ptr, instance, std::string("A format checker was not provided but a format-attribute for this string is present. ") + " cannot be validated for " + format_.second);
else {
try {
root_->format_check()(format_.second, instance);
} catch (const std::exception &ex) {
- e.error("", instance, std::string("Format-checking failed: ") + ex.what());
+ e.error(ptr, instance, std::string("Format-checking failed: ") + ex.what());
}
}
}
@@ -599,23 +599,23 @@ class numeric : public schema
return fabs(res) > std::numeric_limits::epsilon();
}
- void validate(const json &instance, basic_error_handler &e) const override
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
{
T value = instance; // conversion of json to value_type
if (multipleOf_.first && value != 0) // zero is multiple of everything
if (violates_multiple_of(value))
- e.error("", instance, "is not a multiple of " + std::to_string(multipleOf_.second));
+ e.error(ptr, instance, "is not a multiple of " + std::to_string(multipleOf_.second));
if (maximum_.first)
if ((exclusiveMaximum_ && value >= maximum_.second) ||
value > maximum_.second)
- e.error("", instance, "exceeds maximum of " + std::to_string(maximum_.second));
+ e.error(ptr, instance, "exceeds maximum of " + std::to_string(maximum_.second));
if (minimum_.first)
if ((exclusiveMinimum_ && value <= minimum_.second) ||
value < minimum_.second)
- e.error("", instance, "is below minimum of " + std::to_string(minimum_.second));
+ e.error(ptr, instance, "is below minimum of " + std::to_string(minimum_.second));
}
public:
@@ -658,10 +658,10 @@ public:
class null : public schema
{
- void validate(const json &instance, basic_error_handler &e) const override
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
{
if (!instance.is_null())
- e.error("", instance, "expected to be null");
+ e.error(ptr, instance, "expected to be null");
}
public:
@@ -671,7 +671,7 @@ public:
class boolean_type : public schema
{
- void validate(const json &, basic_error_handler &) const override {}
+ void validate(const nlohmann::json::json_pointer &, const json &, basic_error_handler &) const override {}
public:
boolean_type(json &, root_schema *root)
@@ -681,18 +681,18 @@ public:
class boolean : public schema
{
bool true_;
- void validate(const json &instance, basic_error_handler &e) const override
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
{
if (!true_) { // false schema
// empty array
//switch (instance.type()) {
//case json::value_t::array:
// if (instance.size() != 0) // valid false-schema
- // e.error("", instance, "false-schema required empty array");
+ // e.error(ptr, instance, "false-schema required empty array");
// return;
//}
- e.error("", instance, "instance invalid as par false-schema");
+ e.error(ptr, instance, "instance invalid as par false-schema");
}
}
@@ -705,11 +705,11 @@ class required : public schema
{
const std::vector required_;
- void validate(const json &instance, basic_error_handler &e) const override final
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override final
{
for (auto &r : required_)
if (instance.find(r) == instance.end())
- e.error("", instance, "required property '" + r + "' not found in object as a dependency");
+ e.error(ptr, instance, "required property '" + r + "' not found in object as a dependency");
}
public:
@@ -733,46 +733,46 @@ class object : public schema
std::shared_ptr propertyNames_;
- void validate(const json &instance, basic_error_handler &e) const override
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
{
if (maxProperties_.first && instance.size() > maxProperties_.second)
- e.error("", instance, "too many properties.");
+ e.error(ptr, instance, "too many properties.");
if (minProperties_.first && instance.size() < minProperties_.second)
- e.error("", instance, "too few properties.");
+ e.error(ptr, instance, "too few properties.");
for (auto &r : required_)
if (instance.find(r) == instance.end())
- e.error("", instance, "required property '" + r + "' not found in object '");
+ e.error(ptr, instance, "required property '" + r + "' not found in object");
// for each property in instance
for (auto &p : instance.items()) {
if (propertyNames_)
- propertyNames_->validate(p.key(), e);
+ propertyNames_->validate(ptr, p.key(), e);
bool a_prop_or_pattern_matched = false;
auto schema_p = properties_.find(p.key());
// check if it is in "properties"
if (schema_p != properties_.end()) {
a_prop_or_pattern_matched = true;
- schema_p->second->validate(p.value(), e);
+ schema_p->second->validate(ptr + p.key(), p.value(), e);
}
// check all matching patternProperties
for (auto &schema_pp : patternProperties_)
if (REGEX_NAMESPACE::regex_search(p.key(), schema_pp.first)) {
a_prop_or_pattern_matched = true;
- schema_pp.second->validate(p.value(), e);
+ schema_pp.second->validate(ptr + p.key(), p.value(), e);
}
// check additionalProperties as a last resort
if (!a_prop_or_pattern_matched && additionalProperties_)
- additionalProperties_->validate(p.value(), e);
+ additionalProperties_->validate(ptr + p.key(), p.value(), e);
}
for (auto &dep : dependencies_) {
auto prop = instance.find(dep.first);
- if (prop != instance.end()) // if dependency-property is present in instance
- dep.second->validate(instance, e); // validate
+ if (prop != instance.end()) // if dependency-property is present in instance
+ dep.second->validate(ptr + dep.first, instance, e); // validate
}
}
@@ -867,25 +867,28 @@ class array : public schema
std::shared_ptr contains_;
- void validate(const json &instance, basic_error_handler &e) const override
+ void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
{
if (maxItems_.first && instance.size() > maxItems_.second)
- e.error("", instance, "has too many items.");
+ e.error(ptr, instance, "has too many items.");
if (minItems_.first && instance.size() < minItems_.second)
- e.error("", instance, "has too few items.");
+ e.error(ptr, instance, "has too few items.");
if (uniqueItems_) {
for (auto it = instance.cbegin(); it != instance.cend(); ++it) {
auto v = std::find(it + 1, instance.end(), *it);
if (v != instance.end())
- e.error("", instance, "items have to be unique for this array.");
+ e.error(ptr, instance, "items have to be unique for this array.");
}
}
+ size_t index = 0;
if (items_schema_)
- for (auto &i : instance)
- items_schema_->validate(i, e);
+ for (auto &i : instance) {
+ items_schema_->validate(ptr + index, i, e);
+ index++;
+ }
else {
auto item = items_.cbegin();
for (auto &i : instance) {
@@ -900,7 +903,7 @@ class array : public schema
if (!item_validator)
break;
- item_validator->validate(i, e);
+ item_validator->validate(ptr + index, i, e);
}
}
@@ -908,14 +911,14 @@ class array : public schema
bool contained = false;
for (auto &item : instance) {
basic_error_handler local_e;
- contains_->validate(item, local_e);
+ contains_->validate(ptr, item, local_e);
if (!local_e) {
contained = true;
break;
}
}
if (!contained)
- e.error("", instance, "array does not contain required element as per 'contains'");
+ e.error(ptr, instance, "array does not contain required element as per 'contains'");
}
}
@@ -1068,9 +1071,9 @@ std::shared_ptr schema::make(json &schema,
class throwing_error_handler : public basic_error_handler
{
- void error(const std::string &path, const json &instance, const std::string &message)
+ void error(const json::json_pointer &ptr, const json &instance, const std::string &message)
{
- throw std::invalid_argument(std::string("At ") + path + " of " + instance.dump() + " - " + message + "\n");
+ throw std::invalid_argument(std::string("At ") + ptr.to_string() + " of " + instance.dump() + " - " + message + "\n");
}
};
@@ -1089,9 +1092,9 @@ json_validator::json_validator(std::function loa
// move constructor, destructor and move assignment operator can be defaulted here
// where root_schema is a complete type
-json_validator::json_validator(json_validator&&) = default;
+json_validator::json_validator(json_validator &&) = default;
json_validator::~json_validator() = default;
-json_validator& json_validator::operator=(json_validator&&) = default;
+json_validator &json_validator::operator=(json_validator &&) = default;
void json_validator::set_root_schema(const json &schema)
{
@@ -1106,7 +1109,8 @@ void json_validator::validate(const json &instance)
void json_validator::validate(const json &instance, basic_error_handler &err)
{
- root_->validate(instance, err);
+ nlohmann::json::json_pointer ptr;
+ root_->validate(ptr, instance, err);
}
} // namespace json_schema
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index b1b1c01..5d08e41 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -22,4 +22,7 @@ endforeach()
add_executable(uri uri.cpp)
target_link_libraries(uri json-schema-validator)
+add_executable(errors errors.cpp)
+target_link_libraries(errors json-schema-validator)
+
add_test(NAME uri COMMAND uri)
diff --git a/test/errors.cpp b/test/errors.cpp
new file mode 100644
index 0000000..dfa6b88
--- /dev/null
+++ b/test/errors.cpp
@@ -0,0 +1,117 @@
+#include
+
+#include
+
+static int error_count;
+
+#define EXPECT_EQ(a, b) \
+ do { \
+ if (a != b) { \
+ std::cerr << "Failed: '" << a << "' != '" << b << "'\n"; \
+ error_count++; \
+ } \
+ } while (0)
+
+using nlohmann::json;
+using nlohmann::json_schema::json_validator;
+
+namespace
+{
+
+// The schema is defined based upon a string literal
+static json person_schema = R"(
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "A person",
+ "properties": {
+ "name": {
+ "description": "Name",
+ "type": "string"
+ },
+ "age": {
+ "description": "Age of the person",
+ "type": "number",
+ "minimum": 2,
+ "maximum": 200
+ },
+ "phones": {
+ "type": "array",
+ "items": {
+ "type": "number"
+ }
+ }
+ },
+ "required": [
+ "name",
+ "age"
+ ],
+ "type": "object"
+})"_json;
+
+class store_ptr_err_handler : public nlohmann::json_schema::basic_error_handler
+{
+ void error(const nlohmann::json::json_pointer &ptr, const json &instance, const std::string &message) override
+ {
+ nlohmann::json_schema::basic_error_handler::error(ptr, instance, message);
+ std::cerr << "ERROR: '" << ptr << "' - '" << instance << "': " << message << "\n";
+ failed_pointers.push_back(ptr);
+ }
+
+public:
+ std::vector failed_pointers;
+
+ void reset() override
+ {
+ nlohmann::json_schema::basic_error_handler::reset();
+ failed_pointers.clear();
+ }
+};
+
+} // namespace
+
+int main(void)
+{
+ json_validator validator;
+
+ try {
+ validator.set_root_schema(person_schema); // insert root-schema
+ } catch (const std::exception &e) {
+ std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n";
+ return EXIT_FAILURE;
+ }
+
+ store_ptr_err_handler err;
+
+ validator.validate({{"age", 42}, {"name", "John"}}, err); // OK
+ EXPECT_EQ(err.failed_pointers.size(), 0);
+ err.reset();
+
+ validator.validate({{"age", 42}}, err); // no name
+
+ EXPECT_EQ(err.failed_pointers[0].to_string(), "");
+ EXPECT_EQ(err.failed_pointers.size(), 1);
+ err.reset();
+
+ validator.validate({{"street", "Boulevard"}}, err); // no name and no age
+ EXPECT_EQ(err.failed_pointers[0].to_string(), "");
+ EXPECT_EQ(err.failed_pointers[1].to_string(), "");
+ EXPECT_EQ(err.failed_pointers.size(), 2);
+ err.reset();
+
+ validator.validate({{"age", 42}, {"name", 12}}, err); // name must be a string
+ EXPECT_EQ(err.failed_pointers[0].to_string(), "/name");
+ EXPECT_EQ(err.failed_pointers.size(), 1);
+ err.reset();
+
+ validator.validate({
+ {"age", 42},
+ {"name", "John"},
+ {"phones", {1234, "223"}},
+ },
+ err); // name must be a string
+ EXPECT_EQ(err.failed_pointers[0].to_string(), "/phones/1");
+ EXPECT_EQ(err.failed_pointers.size(), 1);
+ err.reset();
+
+ return error_count;
+}