|
@@ -10,12 +10,15 @@
|
|
|
#include <jvalidate/constraint/visitor.h>
|
|
#include <jvalidate/constraint/visitor.h>
|
|
|
#include <jvalidate/detail/expect.h>
|
|
#include <jvalidate/detail/expect.h>
|
|
|
#include <jvalidate/detail/iostream.h>
|
|
#include <jvalidate/detail/iostream.h>
|
|
|
|
|
+#include <jvalidate/detail/pointer.h>
|
|
|
#include <jvalidate/forward.h>
|
|
#include <jvalidate/forward.h>
|
|
|
#include <jvalidate/schema.h>
|
|
#include <jvalidate/schema.h>
|
|
|
#include <jvalidate/status.h>
|
|
#include <jvalidate/status.h>
|
|
|
#include <jvalidate/validation_config.h>
|
|
#include <jvalidate/validation_config.h>
|
|
|
#include <jvalidate/validation_result.h>
|
|
#include <jvalidate/validation_result.h>
|
|
|
|
|
|
|
|
|
|
+#define VISITED(type) std::get<std::unordered_set<type>>(visited_)
|
|
|
|
|
+
|
|
|
#define NOOP_UNLESS_TYPE(etype) RETURN_UNLESS(adapter::Type::etype & document_.type(), Status::Noop)
|
|
#define NOOP_UNLESS_TYPE(etype) RETURN_UNLESS(adapter::Type::etype & document_.type(), Status::Noop)
|
|
|
|
|
|
|
|
#define BREAK_EARLY_IF_NO_RESULT_TREE() \
|
|
#define BREAK_EARLY_IF_NO_RESULT_TREE() \
|
|
@@ -31,19 +34,21 @@ class ValidationVisitor : public constraint::ConstraintVisitor {
|
|
|
private:
|
|
private:
|
|
|
A document_;
|
|
A document_;
|
|
|
detail::Pointer where_;
|
|
detail::Pointer where_;
|
|
|
|
|
+ detail::Pointer schema_path_;
|
|
|
|
|
|
|
|
schema::Node const & schema_;
|
|
schema::Node const & schema_;
|
|
|
|
|
|
|
|
ValidationResult * result_;
|
|
ValidationResult * result_;
|
|
|
- ValidationResult * local_result_;
|
|
|
|
|
|
|
|
|
|
ValidationConfig const & cfg_;
|
|
ValidationConfig const & cfg_;
|
|
|
std::unordered_map<std::string, RE> & regex_cache_;
|
|
std::unordered_map<std::string, RE> & regex_cache_;
|
|
|
|
|
|
|
|
|
|
+ mutable std::tuple<std::unordered_set<size_t>, std::unordered_set<std::string>> visited_;
|
|
|
|
|
+
|
|
|
public:
|
|
public:
|
|
|
ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
|
|
ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
|
|
|
std::unordered_map<std::string, RE> & regex_cache, ValidationResult * result)
|
|
std::unordered_map<std::string, RE> & regex_cache, ValidationResult * result)
|
|
|
- : ValidationVisitor(json, schema, cfg, regex_cache, {}, result) {}
|
|
|
|
|
|
|
+ : ValidationVisitor(json, schema, cfg, regex_cache, {}, {}, result) {}
|
|
|
|
|
|
|
|
Status visit(constraint::TypeConstraint const & cons) const {
|
|
Status visit(constraint::TypeConstraint const & cons) const {
|
|
|
adapter::Type const type = document_.type();
|
|
adapter::Type const type = document_.type();
|
|
@@ -76,8 +81,10 @@ public:
|
|
|
Status visit(constraint::AllOfConstraint const & cons) const {
|
|
Status visit(constraint::AllOfConstraint const & cons) const {
|
|
|
Status rval = Status::Accept;
|
|
Status rval = Status::Accept;
|
|
|
|
|
|
|
|
|
|
+ size_t i = 0;
|
|
|
for (schema::Node const * subschema : cons.children) {
|
|
for (schema::Node const * subschema : cons.children) {
|
|
|
- rval &= validate_subschema(subschema);
|
|
|
|
|
|
|
+ rval &= validate_subschema(subschema, i);
|
|
|
|
|
+ ++i;
|
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -85,10 +92,12 @@ public:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status visit(constraint::AnyOfConstraint const & cons) const {
|
|
Status visit(constraint::AnyOfConstraint const & cons) const {
|
|
|
|
|
+ size_t i = 0;
|
|
|
for (schema::Node const * subschema : cons.children) {
|
|
for (schema::Node const * subschema : cons.children) {
|
|
|
- if (validate_subschema(subschema)) {
|
|
|
|
|
|
|
+ if (validate_subschema(subschema, i)) {
|
|
|
return Status::Accept;
|
|
return Status::Accept;
|
|
|
}
|
|
}
|
|
|
|
|
+ ++i;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return Status::Reject;
|
|
return Status::Reject;
|
|
@@ -96,24 +105,26 @@ public:
|
|
|
|
|
|
|
|
Status visit(constraint::OneOfConstraint const & cons) const {
|
|
Status visit(constraint::OneOfConstraint const & cons) const {
|
|
|
size_t matches = 0;
|
|
size_t matches = 0;
|
|
|
|
|
+ size_t i = 0;
|
|
|
for (schema::Node const * subschema : cons.children) {
|
|
for (schema::Node const * subschema : cons.children) {
|
|
|
- if (validate_subschema(subschema)) {
|
|
|
|
|
|
|
+ if (validate_subschema(subschema, i)) {
|
|
|
++matches;
|
|
++matches;
|
|
|
}
|
|
}
|
|
|
|
|
+ ++i;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return matches == 1 ? Status::Accept : Status::Reject;
|
|
return matches == 1 ? Status::Accept : Status::Reject;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status visit(constraint::NotConstraint const & cons) const {
|
|
Status visit(constraint::NotConstraint const & cons) const {
|
|
|
- return validate_subschema(cons.child) == Status::Reject;
|
|
|
|
|
|
|
+ return validate_subschema(cons.child, "not") == Status::Reject;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status visit(constraint::ConditionalConstraint const & cons) const {
|
|
Status visit(constraint::ConditionalConstraint const & cons) const {
|
|
|
- if (validate_subschema(cons.if_constraint)) {
|
|
|
|
|
- return validate_subschema(cons.then_constraint);
|
|
|
|
|
|
|
+ if (validate_subschema(cons.if_constraint, "if")) {
|
|
|
|
|
+ return validate_subschema(cons.then_constraint, "then");
|
|
|
}
|
|
}
|
|
|
- return validate_subschema(cons.else_constraint);
|
|
|
|
|
|
|
+ return validate_subschema(cons.else_constraint, "else");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status visit(constraint::MaximumConstraint const & cons) const {
|
|
Status visit(constraint::MaximumConstraint const & cons) const {
|
|
@@ -279,7 +290,7 @@ public:
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- rval &= validate_subschema(subschema);
|
|
|
|
|
|
|
+ rval &= validate_subschema(subschema, key);
|
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -358,7 +369,7 @@ public:
|
|
|
for (auto const & [key, _] : document_.as_object()) {
|
|
for (auto const & [key, _] : document_.as_object()) {
|
|
|
// TODO(samjaffe): Should we prefer a std::string adapter like valijson?
|
|
// TODO(samjaffe): Should we prefer a std::string adapter like valijson?
|
|
|
typename A::value_type key_json{key};
|
|
typename A::value_type key_json{key};
|
|
|
- rval &= validate_subschema_on(cons.key_schema, A(key_json), "$$key");
|
|
|
|
|
|
|
+ rval &= validate_subschema_on(cons.key_schema, A(key_json), std::string("$$key"));
|
|
|
}
|
|
}
|
|
|
return rval;
|
|
return rval;
|
|
|
}
|
|
}
|
|
@@ -380,30 +391,32 @@ public:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status visit(constraint::UnevaluatedItemsConstraint const & cons) const {
|
|
Status visit(constraint::UnevaluatedItemsConstraint const & cons) const {
|
|
|
- EXPECT_M(local_result_, "Invalid State - no result object for post-constraint");
|
|
|
|
|
NOOP_UNLESS_TYPE(Array);
|
|
NOOP_UNLESS_TYPE(Array);
|
|
|
|
|
|
|
|
Status rval = Status::Accept;
|
|
Status rval = Status::Accept;
|
|
|
auto array = document_.as_array();
|
|
auto array = document_.as_array();
|
|
|
for (size_t i = 0; i < array.size(); ++i) {
|
|
for (size_t i = 0; i < array.size(); ++i) {
|
|
|
- if (not local_result_->has_visited(i)) {
|
|
|
|
|
|
|
+ if (not VISITED(size_t).contains(i)) {
|
|
|
rval &= validate_subschema_on(cons.subschema, array[i], i);
|
|
rval &= validate_subschema_on(cons.subschema, array[i], i);
|
|
|
}
|
|
}
|
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ return rval;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status visit(constraint::UnevaluatedPropertiesConstraint const & cons) const {
|
|
Status visit(constraint::UnevaluatedPropertiesConstraint const & cons) const {
|
|
|
- EXPECT_M(local_result_, "Invalid State - no result object for post-constraint");
|
|
|
|
|
NOOP_UNLESS_TYPE(Object);
|
|
NOOP_UNLESS_TYPE(Object);
|
|
|
|
|
|
|
|
Status rval = Status::Accept;
|
|
Status rval = Status::Accept;
|
|
|
for (auto const & [key, elem] : document_.as_object()) {
|
|
for (auto const & [key, elem] : document_.as_object()) {
|
|
|
- if (not local_result_->has_visited(key)) {
|
|
|
|
|
|
|
+ if (not VISITED(std::string).contains(key)) {
|
|
|
rval &= validate_subschema_on(cons.subschema, elem, key);
|
|
rval &= validate_subschema_on(cons.subschema, elem, key);
|
|
|
}
|
|
}
|
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ return rval;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status validate() {
|
|
Status validate() {
|
|
@@ -412,30 +425,21 @@ public:
|
|
|
return Status::Reject;
|
|
return Status::Reject;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ValidationResult local_result;
|
|
|
|
|
- if (schema_.requires_result_context() && not local_result_) {
|
|
|
|
|
- // Ensure that we store results even if there aren't any...
|
|
|
|
|
- local_result_ = &local_result;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
Status rval = Status::Noop;
|
|
Status rval = Status::Noop;
|
|
|
if (auto ref = schema_.reference_schema()) {
|
|
if (auto ref = schema_.reference_schema()) {
|
|
|
- rval = validate_subschema(*ref);
|
|
|
|
|
|
|
+ rval = validate_subschema(*ref, "$ref");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ detail::Pointer const current_schema = schema_path_;
|
|
|
for (auto const & [key, p_constraint] : schema_.constraints()) {
|
|
for (auto const & [key, p_constraint] : schema_.constraints()) {
|
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
|
- if (result_) {
|
|
|
|
|
- result_->constraint(key);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ schema_path_ = current_schema / key;
|
|
|
rval &= p_constraint->accept(*this);
|
|
rval &= p_constraint->accept(*this);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for (auto const & [key, p_constraint] : schema_.post_constraints()) {
|
|
for (auto const & [key, p_constraint] : schema_.post_constraints()) {
|
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
BREAK_EARLY_IF_NO_RESULT_TREE();
|
|
|
- if (result_) {
|
|
|
|
|
- result_->constraint(key);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ schema_path_ = current_schema / key;
|
|
|
rval &= p_constraint->accept(*this);
|
|
rval &= p_constraint->accept(*this);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -450,20 +454,21 @@ private:
|
|
|
std::stringstream ss;
|
|
std::stringstream ss;
|
|
|
using ::jvalidate::operator<<;
|
|
using ::jvalidate::operator<<;
|
|
|
[[maybe_unused]] int _[] = {(ss << std::forward<Args>(args), 0)...};
|
|
[[maybe_unused]] int _[] = {(ss << std::forward<Args>(args), 0)...};
|
|
|
- result_->message(ss.str());
|
|
|
|
|
|
|
+ result_->add_error(where_, schema_path_, ss.str());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
|
|
ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
|
|
|
std::unordered_map<std::string, RE> & regex_cache,
|
|
std::unordered_map<std::string, RE> & regex_cache,
|
|
|
- detail::Pointer const & where, ValidationResult * result,
|
|
|
|
|
- ValidationResult * local_result = nullptr)
|
|
|
|
|
- : document_(json), where_(where), schema_(schema), cfg_(cfg), regex_cache_(regex_cache),
|
|
|
|
|
- result_(result), local_result_(local_result ?: result_) {}
|
|
|
|
|
|
|
+ detail::Pointer const & where, detail::Pointer const & schema_path,
|
|
|
|
|
+ ValidationResult * result)
|
|
|
|
|
+ : document_(json), where_(where), schema_path_(schema_path), schema_(schema), cfg_(cfg),
|
|
|
|
|
+ regex_cache_(regex_cache), result_(result) {}
|
|
|
|
|
|
|
|
- Status validate_subschema(schema::Node const * subschema) const {
|
|
|
|
|
|
|
+ template <typename K>
|
|
|
|
|
+ Status validate_subschema(schema::Node const * subschema, K const & key) const {
|
|
|
EXPECT(subschema != &schema_); // TODO(samjaffe) - Figure out what's causing this infinite loop
|
|
EXPECT(subschema != &schema_); // TODO(samjaffe) - Figure out what's causing this infinite loop
|
|
|
- return ValidationVisitor(document_, *subschema, cfg_, regex_cache_, where_, result_,
|
|
|
|
|
- local_result_)
|
|
|
|
|
|
|
+ return ValidationVisitor(document_, *subschema, cfg_, regex_cache_, where_, schema_path_ / key,
|
|
|
|
|
+ result_)
|
|
|
.validate();
|
|
.validate();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -473,13 +478,14 @@ private:
|
|
|
ValidationResult next;
|
|
ValidationResult next;
|
|
|
ValidationResult * pnext = result_ ? &next : nullptr;
|
|
ValidationResult * pnext = result_ ? &next : nullptr;
|
|
|
|
|
|
|
|
- auto status =
|
|
|
|
|
- ValidationVisitor(document, *subschema, cfg_, regex_cache_, where_ / key, pnext).validate();
|
|
|
|
|
- if (status != Status::Noop and local_result_) {
|
|
|
|
|
- local_result_->visit(key);
|
|
|
|
|
|
|
+ auto status = ValidationVisitor(document, *subschema, cfg_, regex_cache_, where_ / key,
|
|
|
|
|
+ schema_path_, pnext)
|
|
|
|
|
+ .validate();
|
|
|
|
|
+ if (status != Status::Noop) {
|
|
|
|
|
+ VISITED(K).insert(key);
|
|
|
}
|
|
}
|
|
|
if (status == Status::Reject and result_) {
|
|
if (status == Status::Reject and result_) {
|
|
|
- result_->error(key, std::move(next));
|
|
|
|
|
|
|
+ result_->add_error(std::move(next));
|
|
|
}
|
|
}
|
|
|
return status;
|
|
return status;
|
|
|
}
|
|
}
|