|
@@ -1,5 +1,6 @@
|
|
|
#pragma once
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
+#include <tuple>
|
|
|
#include <unordered_map>
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
#include <jvalidate/constraint/array_constraint.h>
|
|
#include <jvalidate/constraint/array_constraint.h>
|
|
@@ -17,7 +18,7 @@
|
|
|
#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 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)
|
|
|
|
|
|
|
@@ -31,24 +32,27 @@
|
|
|
namespace jvalidate {
|
|
namespace jvalidate {
|
|
|
template <Adapter A, RegexEngine RE>
|
|
template <Adapter A, RegexEngine RE>
|
|
|
class ValidationVisitor : public constraint::ConstraintVisitor {
|
|
class ValidationVisitor : public constraint::ConstraintVisitor {
|
|
|
|
|
+private:
|
|
|
|
|
+ using VisitedAnnotation = std::tuple<std::unordered_set<size_t>, std::unordered_set<std::string>>;
|
|
|
|
|
+
|
|
|
private:
|
|
private:
|
|
|
A document_;
|
|
A document_;
|
|
|
detail::Pointer where_;
|
|
detail::Pointer where_;
|
|
|
detail::Pointer schema_path_;
|
|
detail::Pointer schema_path_;
|
|
|
|
|
|
|
|
- schema::Node const & schema_;
|
|
|
|
|
|
|
+ schema::Node const * schema_;
|
|
|
|
|
|
|
|
ValidationResult * result_;
|
|
ValidationResult * 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_;
|
|
|
|
|
|
|
+ mutable VisitedAnnotation * visited_ = nullptr;
|
|
|
|
|
|
|
|
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) {}
|
|
|
|
|
|
|
+ : document_(json), schema_(&schema), result_(result), cfg_(cfg), regex_cache_(regex_cache) {}
|
|
|
|
|
|
|
|
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();
|
|
@@ -120,7 +124,11 @@ public:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status visit(constraint::NotConstraint const & cons) const {
|
|
Status visit(constraint::NotConstraint const & cons) const {
|
|
|
- return validate_subschema(cons.child, "") == Status::Reject;
|
|
|
|
|
|
|
+ VisitedAnnotation * suppress = nullptr;
|
|
|
|
|
+ std::swap(suppress, visited_);
|
|
|
|
|
+ auto rval = validate_subschema(cons.child, "") == Status::Reject;
|
|
|
|
|
+ std::swap(suppress, visited_);
|
|
|
|
|
+ return rval;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status visit(constraint::ConditionalConstraint const & cons) const {
|
|
Status visit(constraint::ConditionalConstraint const & cons) const {
|
|
@@ -395,6 +403,9 @@ public:
|
|
|
|
|
|
|
|
Status visit(constraint::UnevaluatedItemsConstraint const & cons) const {
|
|
Status visit(constraint::UnevaluatedItemsConstraint const & cons) const {
|
|
|
NOOP_UNLESS_TYPE(Array);
|
|
NOOP_UNLESS_TYPE(Array);
|
|
|
|
|
+ if (not visited_) {
|
|
|
|
|
+ return Status::Reject;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
Status rval = Status::Accept;
|
|
Status rval = Status::Accept;
|
|
|
auto array = document_.as_array();
|
|
auto array = document_.as_array();
|
|
@@ -410,6 +421,9 @@ public:
|
|
|
|
|
|
|
|
Status visit(constraint::UnevaluatedPropertiesConstraint const & cons) const {
|
|
Status visit(constraint::UnevaluatedPropertiesConstraint const & cons) const {
|
|
|
NOOP_UNLESS_TYPE(Object);
|
|
NOOP_UNLESS_TYPE(Object);
|
|
|
|
|
+ if (not visited_) {
|
|
|
|
|
+ return Status::Reject;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
Status rval = Status::Accept;
|
|
Status rval = Status::Accept;
|
|
|
for (auto const & [key, elem] : document_.as_object()) {
|
|
for (auto const & [key, elem] : document_.as_object()) {
|
|
@@ -423,29 +437,34 @@ public:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Status validate() {
|
|
Status validate() {
|
|
|
- if (auto const & reject = schema_.rejects_all()) {
|
|
|
|
|
|
|
+ if (auto const & reject = schema_->rejects_all()) {
|
|
|
add_error(*reject);
|
|
add_error(*reject);
|
|
|
return Status::Reject;
|
|
return Status::Reject;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (schema_.accepts_all()) {
|
|
|
|
|
|
|
+ if (schema_->accepts_all()) {
|
|
|
// An accept-all schema is not No-Op for the purpose of unevaluated*
|
|
// An accept-all schema is not No-Op for the purpose of unevaluated*
|
|
|
return Status::Accept;
|
|
return Status::Accept;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ VisitedAnnotation annotate;
|
|
|
|
|
+ if (schema_->requires_result_context() and not visited_) {
|
|
|
|
|
+ visited_ = &annotate;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
Status rval = Status::Noop;
|
|
Status rval = Status::Noop;
|
|
|
- if (auto ref = schema_.reference_schema()) {
|
|
|
|
|
|
|
+ if (auto ref = schema_->reference_schema()) {
|
|
|
rval = validate_subschema(*ref, "$ref");
|
|
rval = validate_subschema(*ref, "$ref");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
detail::Pointer const current_schema = schema_path_;
|
|
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();
|
|
|
schema_path_ = current_schema / 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();
|
|
|
schema_path_ = current_schema / key;
|
|
schema_path_ = current_schema / key;
|
|
|
rval &= p_constraint->accept(*this);
|
|
rval &= p_constraint->accept(*this);
|
|
@@ -469,38 +488,42 @@ private:
|
|
|
to.insert(from.begin(), from.end());
|
|
to.insert(from.begin(), from.end());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
|
|
|
|
|
- std::unordered_map<std::string, RE> & regex_cache,
|
|
|
|
|
- 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) {}
|
|
|
|
|
-
|
|
|
|
|
template <typename K>
|
|
template <typename K>
|
|
|
Status validate_subschema(schema::Node const * subschema, K const & key) const {
|
|
Status validate_subschema(schema::Node const * subschema, K const & key) const {
|
|
|
- EXPECT(subschema != &schema_); // TODO(samjaffe) - Figure out what's causing this infinite loop
|
|
|
|
|
- ValidationVisitor next(document_, *subschema, cfg_, regex_cache_, where_, schema_path_ / key,
|
|
|
|
|
- result_);
|
|
|
|
|
|
|
+ EXPECT(subschema != schema_); // TODO(samjaffe) - Figure out what's causing this infinite loop
|
|
|
|
|
+
|
|
|
|
|
+ VisitedAnnotation annotate;
|
|
|
|
|
+
|
|
|
|
|
+ ValidationVisitor next = *this;
|
|
|
|
|
+ next.schema_path_ /= key;
|
|
|
|
|
+ std::tie(next.schema_, next.visited_) =
|
|
|
|
|
+ std::forward_as_tuple(subschema, visited_ ? &annotate : nullptr);
|
|
|
|
|
+
|
|
|
Status rval = next.validate();
|
|
Status rval = next.validate();
|
|
|
- merge_visited(std::get<0>(visited_), std::get<0>(next.visited_));
|
|
|
|
|
- merge_visited(std::get<1>(visited_), std::get<1>(next.visited_));
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (rval == Status::Accept and visited_) {
|
|
|
|
|
+ merge_visited(std::get<0>(*visited_), std::get<0>(annotate));
|
|
|
|
|
+ merge_visited(std::get<1>(*visited_), std::get<1>(annotate));
|
|
|
|
|
+ }
|
|
|
return rval;
|
|
return rval;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <typename K>
|
|
template <typename K>
|
|
|
Status validate_subschema_on(schema::Node const * subschema, A const & document,
|
|
Status validate_subschema_on(schema::Node const * subschema, A const & document,
|
|
|
K const & key) const {
|
|
K const & key) const {
|
|
|
- ValidationResult next;
|
|
|
|
|
- ValidationResult * pnext = result_ ? &next : nullptr;
|
|
|
|
|
|
|
+ ValidationResult result;
|
|
|
|
|
+
|
|
|
|
|
+ ValidationVisitor next = *this;
|
|
|
|
|
+ next.where_ /= key;
|
|
|
|
|
+ std::tie(next.document_, next.schema_, next.result_, next.visited_) =
|
|
|
|
|
+ std::forward_as_tuple(document, subschema, result_ ? &result : nullptr, nullptr);
|
|
|
|
|
|
|
|
- auto status = ValidationVisitor(document, *subschema, cfg_, regex_cache_, where_ / key,
|
|
|
|
|
- schema_path_, pnext)
|
|
|
|
|
- .validate();
|
|
|
|
|
- if (status != Status::Noop) {
|
|
|
|
|
|
|
+ auto status = next.validate();
|
|
|
|
|
+ if (status == Status::Accept and visited_) {
|
|
|
VISITED(K).insert(key);
|
|
VISITED(K).insert(key);
|
|
|
}
|
|
}
|
|
|
if (status == Status::Reject and result_) {
|
|
if (status == Status::Reject and result_) {
|
|
|
- result_->add_error(std::move(next));
|
|
|
|
|
|
|
+ result_->add_error(std::move(result));
|
|
|
}
|
|
}
|
|
|
return status;
|
|
return status;
|
|
|
}
|
|
}
|