From 75853c11ee0455c2702182f85531ca441a726c87 Mon Sep 17 00:00:00 2001 From: Sven Fink Date: Tue, 3 Mar 2020 10:23:56 +0100 Subject: [PATCH] First implementation of default generation --- app/readme.cpp | 22 +++-- src/json-validator.cpp | 175 ++++++++++++++++++++--------------- src/nlohmann/json-schema.hpp | 4 +- 3 files changed, 120 insertions(+), 81 deletions(-) diff --git a/app/readme.cpp b/app/readme.cpp index da95fed..863cd07 100644 --- a/app/readme.cpp +++ b/app/readme.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include @@ -21,7 +21,16 @@ static json person_schema = R"( "type": "number", "minimum": 2, "maximum": 200 - } + }, + "address":{ + "type": "object", + "properties":{ + "street":{ + "type": "string", + "default": "Abbey Road" + } + } + } }, "required": [ "name", @@ -34,7 +43,8 @@ static json person_schema = R"( // The people are defined with brace initialization static json bad_person = {{"age", 42}}; -static json good_person = {{"name", "Albert"}, {"age", 42}}; +static json good_person = {{"name", "Albert"}, {"age", 42}, {"address", {{"street", "Main Street"}}}}; +static json good_defaulted_person = {{"name", "Knut"}, {"age", 69}, {"address", {}}}; int main() { @@ -51,12 +61,13 @@ int main() /* json-parse the people - API of 1.0.0, default throwing error handler */ - for (auto &person : {bad_person, good_person}) { + for (auto &person : {bad_person, good_person, good_defaulted_person}) { std::cout << "About to validate this person:\n" << std::setw(2) << person << std::endl; try { - validator.validate(person); // validate the document - uses the default throwing error-handler + auto defaultPatch = validator.validate(person); // validate the document - uses the default throwing error-handler std::cout << "Validation succeeded\n"; + std::cout << "Patch with defaults: " << defaultPatch.dump(2) << std::endl; } catch (const std::exception &e) { std::cerr << "Validation failed, here is why: " << e.what() << "\n"; } @@ -72,7 +83,6 @@ int main() } }; - for (auto &person : {bad_person, good_person}) { std::cout << "About to validate this person:\n" << std::setw(2) << person << std::endl; diff --git a/src/json-validator.cpp b/src/json-validator.cpp index f83eff9..9a17640 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -30,6 +30,8 @@ using namespace nlohmann::json_schema; namespace { +static const json EmptyDefault{}; + class schema { protected: @@ -41,7 +43,12 @@ public: schema(root_schema *root) : root_(root) {} - virtual void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const = 0; + virtual void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const = 0; + + virtual const json &defaultValue(const json::json_pointer &, const json &, error_handler &) const + { + return EmptyDefault; + } static std::shared_ptr make(json &schema, root_schema *root, @@ -49,46 +56,29 @@ public: std::vector uris); }; -class schema_with_default : public schema -{ - json defaultValue_{}; - -public: - schema_with_default(json &sch, root_schema *root) - : schema(root) - { - const auto attr = sch.find("default"); - if (attr != sch.end()) { - defaultValue_ = std::move(attr.value()); - sch.erase(attr); - } - } - - schema_with_default(const json &sch, root_schema *root) - : schema(root) - { - const auto attr = sch.find("default"); - if (attr != sch.end()) { - defaultValue_ = attr.value(); - } - } - - virtual ~schema_with_default() = default; -}; - class schema_ref : public schema { const std::string id_; std::shared_ptr target_; - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const final + void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const final { if (target_) - target_->validate(ptr, instance, e); + target_->validate(ptr, instance, patch, e); else e.error(ptr, instance, "unresolved schema-reference " + id_); } + const json &defaultValue(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + { + if (target_) + target_->defaultValue(ptr, instance, e); + else + e.error(ptr, instance, "unresolved schema-reference " + id_); + + return EmptyDefault; + } + public: schema_ref(const std::string &id, root_schema *root) : schema(root), id_(id) {} @@ -237,13 +227,23 @@ public: } while (1); } - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const final + void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const final { if (root_) - root_->validate(ptr, instance, e); + root_->validate(ptr, instance, patch, e); else e.error(ptr, "", "no root schema has yet been set for validating an instance"); } + + const json &defaultValue(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + { + if (root_) + root_->defaultValue(ptr, instance, e); + else + e.error(ptr, "", "no root schema has yet been set for validating an instance"); + + return EmptyDefault; + } }; } // namespace json_schema @@ -277,15 +277,20 @@ class logical_not : public schema { std::shared_ptr subschema_; - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const final + void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const final { first_error_handler esub; - subschema_->validate(ptr, instance, esub); + subschema_->validate(ptr, instance, patch, esub); if (!esub) e.error(ptr, instance, "the subschema has succeeded, but it is required to not validate"); } + const json &defaultValue(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + { + return subschema_->defaultValue(ptr, instance, e); + } + public: logical_not(json &sch, root_schema *root, @@ -307,13 +312,13 @@ class logical_combination : public schema { std::vector> subschemata_; - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const final + void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const final { size_t count = 0; for (auto &s : subschemata_) { first_error_handler esub; - s->validate(ptr, instance, esub); + s->validate(ptr, instance, patch, esub); if (!esub) count++; @@ -377,6 +382,7 @@ bool logical_combination::is_validate_complete(const json &instance, cons class type_schema : public schema { + json defaultValue_{}; std::vector> type_; std::pair enum_, const_; std::vector> logic_; @@ -389,13 +395,18 @@ class type_schema : public schema std::shared_ptr if_, then_, else_; - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const override final + const json &defaultValue(const json::json_pointer &, const json &, error_handler &) const override + { + return defaultValue_; + } + + void validate(const json::json_pointer &ptr, const json &instance, json &patch, 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(ptr, instance, e); + type->validate(ptr, instance, patch, e); else e.error(ptr, instance, "unexpected instance type"); @@ -416,18 +427,18 @@ class type_schema : public schema e.error(ptr, instance, "instance not const"); for (auto l : logic_) - l->validate(ptr, instance, e); + l->validate(ptr, instance, patch, e); if (if_) { first_error_handler err; - if_->validate(ptr, instance, err); + if_->validate(ptr, instance, patch, err); if (!err) { if (then_) - then_->validate(ptr, instance, e); + then_->validate(ptr, instance, patch, e); } else { if (else_) - else_->validate(ptr, instance, e); + else_->validate(ptr, instance, patch, e); } } } @@ -479,6 +490,11 @@ public: sch.erase(attr); } + const auto defaultAttr = sch.find("default"); + if (defaultAttr != sch.end()) { + defaultValue_ = defaultAttr.value(); + } + for (auto &key : known_keywords) sch.erase(key); @@ -550,7 +566,7 @@ public: } }; -class string : public schema_with_default +class string : public schema { std::pair maxLength_{false, 0}; std::pair minLength_{false, 0}; @@ -571,7 +587,7 @@ class string : public schema_with_default return len; } - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override { if (minLength_.first) { if (utf8_length(instance) < minLength_.second) { @@ -610,7 +626,7 @@ class string : public schema_with_default public: string(json &sch, root_schema *root) - : schema_with_default(sch, root) + : schema(root) { auto attr = sch.find("maxLength"); if (attr != sch.end()) { @@ -643,7 +659,7 @@ public: }; template -class numeric : public schema_with_default +class numeric : public schema { std::pair maximum_{false, 0}; std::pair minimum_{false, 0}; @@ -661,7 +677,7 @@ class numeric : public schema_with_default return std::fabs(res) > std::fabs(eps); } - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override { T value = instance; // conversion of json to value_type @@ -682,7 +698,7 @@ class numeric : public schema_with_default public: numeric(const json &sch, root_schema *root, std::set &kw) - : schema_with_default(sch, root) + : schema(root) { auto attr = sch.find("maximum"); if (attr != sch.end()) { @@ -720,7 +736,7 @@ public: class null : public schema { - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override { if (!instance.is_null()) e.error(ptr, instance, "expected to be null"); @@ -733,17 +749,17 @@ public: class boolean_type : public schema { - void validate(const json::json_pointer &, const json &, error_handler &) const override {} + void validate(const json::json_pointer &, const json &, json &, error_handler &) const override {} public: boolean_type(json &, root_schema *root) : schema(root) {} }; -class boolean : public schema_with_default +class boolean : public schema { bool true_; - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override { if (!true_) { // false schema // empty array @@ -760,14 +776,14 @@ class boolean : public schema_with_default public: boolean(json &sch, root_schema *root) - : schema_with_default(sch, root), true_(sch) {} + : schema(root), true_(sch) {} }; class required : public schema { const std::vector required_; - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const override final + void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override final { for (auto &r : required_) if (instance.find(r) == instance.end()) @@ -779,7 +795,7 @@ public: : schema(root), required_(r) {} }; -class object : public schema_with_default +class object : public schema { std::pair maxProperties_{false, 0}; std::pair minProperties_{false, 0}; @@ -795,7 +811,7 @@ class object : public schema_with_default std::shared_ptr propertyNames_; - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const override { if (maxProperties_.first && instance.size() > maxProperties_.second) e.error(ptr, instance, "too many properties"); @@ -810,14 +826,14 @@ class object : public schema_with_default // for each property in instance for (auto &p : instance.items()) { if (propertyNames_) - propertyNames_->validate(ptr, p.key(), e); + propertyNames_->validate(ptr, p.key(), patch, 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(ptr / p.key(), p.value(), e); + schema_p->second->validate(ptr / p.key(), p.value(), patch, e); } #ifndef NO_STD_REGEX @@ -825,23 +841,34 @@ class object : public schema_with_default 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(ptr / p.key(), p.value(), e); + schema_pp.second->validate(ptr / p.key(), p.value(), patch, e); } #endif // check additionalProperties as a last resort if (!a_prop_or_pattern_matched && additionalProperties_) { first_error_handler additional_prop_err; - additionalProperties_->validate(ptr / p.key(), p.value(), additional_prop_err); + additionalProperties_->validate(ptr / p.key(), p.value(), patch, additional_prop_err); if (additional_prop_err) e.error(ptr, instance, "validation failed for additional property '" + p.key() + "': " + additional_prop_err.message_); } } + // reverse search + for (auto const &prop : properties_) { + const auto finding = instance.find(prop.first); + if (instance.end() == finding) { // if the prop is not in the instance + const auto &defaultValue = prop.second->defaultValue(ptr, instance, e); + if (!defaultValue.empty()) { // if default value is available + patch[ptr / prop.first] = defaultValue; + } + } + } + for (auto &dep : dependencies_) { auto prop = instance.find(dep.first); - if (prop != instance.end()) // if dependency-property is present in instance - dep.second->validate(ptr / dep.first, instance, e); // validate + if (prop != instance.end()) // if dependency-property is present in instance + dep.second->validate(ptr / dep.first, instance, patch, e); // validate } } @@ -849,7 +876,7 @@ public: object(json &sch, root_schema *root, const std::vector &uris) - : schema_with_default(sch, root) + : schema(root) { auto attr = sch.find("maxProperties"); if (attr != sch.end()) { @@ -923,7 +950,7 @@ public: } }; -class array : public schema_with_default +class array : public schema { std::pair maxItems_{false, 0}; std::pair minItems_{false, 0}; @@ -936,7 +963,7 @@ class array : public schema_with_default std::shared_ptr contains_; - void validate(const json::json_pointer &ptr, const json &instance, error_handler &e) const override + void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const override { if (maxItems_.first && instance.size() > maxItems_.second) e.error(ptr, instance, "array has too many items"); @@ -955,7 +982,7 @@ class array : public schema_with_default size_t index = 0; if (items_schema_) for (auto &i : instance) { - items_schema_->validate(ptr / index, i, e); + items_schema_->validate(ptr / index, i, patch, e); index++; } else { @@ -972,7 +999,7 @@ class array : public schema_with_default if (!item_validator) break; - item_validator->validate(ptr / index, i, e); + item_validator->validate(ptr / index, i, patch, e); } } @@ -980,7 +1007,7 @@ class array : public schema_with_default bool contained = false; for (auto &item : instance) { first_error_handler local_e; - contains_->validate(ptr, item, local_e); + contains_->validate(ptr, item, patch, local_e); if (!local_e) { contained = true; break; @@ -993,7 +1020,7 @@ class array : public schema_with_default public: array(json &sch, root_schema *root, const std::vector &uris) - : schema_with_default(sch, root) + : schema(root) { auto attr = sch.find("maxItems"); if (attr != sch.end()) { @@ -1188,16 +1215,18 @@ void json_validator::set_root_schema(json &&schema) root_->set_root_schema(std::move(schema)); } -void json_validator::validate(const json &instance) const +json json_validator::validate(const json &instance) const { throwing_error_handler err; - validate(instance, err); + return validate(instance, err); } -void json_validator::validate(const json &instance, error_handler &err) const +json json_validator::validate(const json &instance, error_handler &err) const { json::json_pointer ptr; - root_->validate(ptr, instance, err); + json mergePatch{}; + root_->validate(ptr, instance, mergePatch, err); + return mergePatch; } } // namespace json_schema diff --git a/src/nlohmann/json-schema.hpp b/src/nlohmann/json-schema.hpp index 2d18411..baa9719 100644 --- a/src/nlohmann/json-schema.hpp +++ b/src/nlohmann/json-schema.hpp @@ -186,10 +186,10 @@ public: void set_root_schema(json &&); // validate a json-document based on the root-schema - void validate(const json &) const; + json validate(const json &) const; // validate a json-document based on the root-schema with a custom error-handler - void validate(const json &, error_handler &) const; + json validate(const json &, error_handler &) const; }; } // namespace json_schema