|
|
@@ -1,6 +1,7 @@
|
|
|
#pragma once
|
|
|
|
|
|
#include <tuple>
|
|
|
+#include <type_traits>
|
|
|
#include <unordered_map>
|
|
|
|
|
|
#include <jvalidate/constraint/array_constraint.h>
|
|
|
@@ -62,19 +63,20 @@ public:
|
|
|
|
|
|
Status visit(constraint::TypeConstraint const & cons) const {
|
|
|
adapter::Type const type = document_.type();
|
|
|
+
|
|
|
for (adapter::Type const accept : cons.types) {
|
|
|
if (type == accept) {
|
|
|
- return note(Status::Accept, "type (", type, ") is one of [", cons.types, "]");
|
|
|
+ return result(Status::Accept, type, " is in types [", cons.types, "]");
|
|
|
}
|
|
|
if (accept == adapter::Type::Number && type == adapter::Type::Integer) {
|
|
|
- return note(Status::Accept, "type (", type, ") is one of [", cons.types, "]");
|
|
|
+ return result(Status::Accept, type, " is in types [", cons.types, "]");
|
|
|
}
|
|
|
if (accept == adapter::Type::Integer && type == adapter::Type::Number &&
|
|
|
detail::is_json_integer(document_.as_number())) {
|
|
|
- return note(Status::Accept, "type (", type, ") is one of [", cons.types, "]");
|
|
|
+ return result(Status::Accept, type, " is in types [", cons.types, "]");
|
|
|
}
|
|
|
}
|
|
|
- return note(Status::Reject, "type (", type, ") is not one of [", cons.types, "]");
|
|
|
+ return result(Status::Reject, type, " is not in types [", cons.types, "]");
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::ExtensionConstraint const & cons) const {
|
|
|
@@ -87,10 +89,10 @@ public:
|
|
|
};
|
|
|
for (auto const & [index, option] : detail::enumerate(cons.enumeration)) {
|
|
|
if (option->apply(is_equal)) {
|
|
|
- return note(Status::Accept, "value is enum ", index);
|
|
|
+ return result(Status::Accept, index);
|
|
|
}
|
|
|
}
|
|
|
- return note(Status::Reject, "value is none of the enums");
|
|
|
+ return Status::Reject;
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::AllOfConstraint const & cons) const {
|
|
|
@@ -106,9 +108,9 @@ public:
|
|
|
}
|
|
|
|
|
|
if (rval == Status::Reject) {
|
|
|
- return note(rval, "does not validate subschemas ", unmatched);
|
|
|
+ return result(rval, "does not validate subschemas ", unmatched);
|
|
|
}
|
|
|
- return note(rval, "validates all subschemas");
|
|
|
+ return result(rval, "validates all subschemas");
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::AnyOfConstraint const & cons) const {
|
|
|
@@ -123,9 +125,9 @@ public:
|
|
|
}
|
|
|
|
|
|
if (first_validated.has_value()) {
|
|
|
- return note(Status::Accept, "validates subschema ", *first_validated);
|
|
|
+ return result(Status::Accept, "validates subschema ", *first_validated);
|
|
|
}
|
|
|
- return note(Status::Reject, "validates none of the subschemas");
|
|
|
+ return result(Status::Reject, "validates none of the subschemas");
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::OneOfConstraint const & cons) const {
|
|
|
@@ -139,9 +141,9 @@ public:
|
|
|
}
|
|
|
|
|
|
if (matches.size() == 1) {
|
|
|
- return note(Status::Accept, "validates subschema ", *matches.begin());
|
|
|
+ return result(Status::Accept, "validates subschema ", *matches.begin());
|
|
|
}
|
|
|
- return note(Status::Reject, "validates multiple subschemas ", matches);
|
|
|
+ return result(Status::Reject, "validates multiple subschemas ", matches);
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::NotConstraint const & cons) const {
|
|
|
@@ -149,9 +151,6 @@ public:
|
|
|
scoped_state(tracking_, !tracking_);
|
|
|
bool const rejected = validate_subschema(cons.child) == Status::Reject;
|
|
|
|
|
|
- if (not rejected) {
|
|
|
- annotate("actually validates subschema");
|
|
|
- }
|
|
|
return rejected;
|
|
|
}
|
|
|
|
|
|
@@ -170,15 +169,15 @@ public:
|
|
|
switch (document_.type()) {
|
|
|
case adapter::Type::Integer:
|
|
|
if (int64_t value = document_.as_integer(); not cons(value)) {
|
|
|
- return note(Status::Reject, value, cons.exclusive ? " >= " : " > ", cons.value);
|
|
|
+ return result(Status::Reject, value, cons.exclusive ? " >= " : " > ", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, value, cons.exclusive ? " < " : " <= ", cons.value);
|
|
|
+ return result(Status::Accept, value, cons.exclusive ? " < " : " <= ", cons.value);
|
|
|
}
|
|
|
case adapter::Type::Number:
|
|
|
if (double value = document_.as_number(); not cons(value)) {
|
|
|
- return note(Status::Reject, value, cons.exclusive ? " >= " : " > ", cons.value);
|
|
|
+ return result(Status::Reject, value, cons.exclusive ? " >= " : " > ", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, value, cons.exclusive ? " < " : " <= ", cons.value);
|
|
|
+ return result(Status::Accept, value, cons.exclusive ? " < " : " <= ", cons.value);
|
|
|
}
|
|
|
default:
|
|
|
return Status::Noop;
|
|
|
@@ -189,15 +188,15 @@ public:
|
|
|
switch (document_.type()) {
|
|
|
case adapter::Type::Integer:
|
|
|
if (int64_t value = document_.as_integer(); not cons(value)) {
|
|
|
- return note(Status::Reject, value, cons.exclusive ? " <= " : " < ", cons.value);
|
|
|
+ return result(Status::Reject, value, cons.exclusive ? " <= " : " < ", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, value, cons.exclusive ? " > " : " >= ", cons.value);
|
|
|
+ return result(Status::Accept, value, cons.exclusive ? " > " : " >= ", cons.value);
|
|
|
}
|
|
|
case adapter::Type::Number:
|
|
|
if (double value = document_.as_number(); not cons(value)) {
|
|
|
- return note(Status::Reject, value, cons.exclusive ? " <= " : " < ", cons.value);
|
|
|
+ return result(Status::Reject, value, cons.exclusive ? " <= " : " < ", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, value, cons.exclusive ? " > " : " >= ", cons.value);
|
|
|
+ return result(Status::Accept, value, cons.exclusive ? " > " : " >= ", cons.value);
|
|
|
}
|
|
|
default:
|
|
|
return Status::Noop;
|
|
|
@@ -209,9 +208,9 @@ public:
|
|
|
RETURN_UNLESS(type == adapter::Type::Number || type == adapter::Type::Integer, Status::Noop);
|
|
|
|
|
|
if (double value = document_.as_number(); not cons(value)) {
|
|
|
- return note(Status::Reject, value, " is not a multiple of ", cons.value);
|
|
|
+ return result(Status::Reject, value, " is not a multiple of ", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, value, " is a multiple of ", cons.value);
|
|
|
+ return result(Status::Accept, value, " is a multiple of ", cons.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -219,9 +218,9 @@ public:
|
|
|
NOOP_UNLESS_TYPE(String);
|
|
|
std::string const str = document_.as_string();
|
|
|
if (int64_t len = detail::length(str); len > cons.value) {
|
|
|
- return note(Status::Reject, "'", str, "' of length ", len, " is >", cons.value);
|
|
|
+ return result(Status::Reject, "'", str, "' of length ", len, " is >", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, "'", str, "' of length ", len, " is <=", cons.value);
|
|
|
+ return result(Status::Accept, "'", str, "' of length ", len, " is <=", cons.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -229,9 +228,9 @@ public:
|
|
|
NOOP_UNLESS_TYPE(String);
|
|
|
std::string const str = document_.as_string();
|
|
|
if (int64_t len = detail::length(str); len < cons.value) {
|
|
|
- return note(Status::Reject, "'", str, "' of length ", len, " is <", cons.value);
|
|
|
+ return result(Status::Reject, "'", str, "' of length ", len, " is <", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, "'", str, "' of length ", len, " is >=", cons.value);
|
|
|
+ return result(Status::Accept, "'", str, "' of length ", len, " is >=", cons.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -241,9 +240,9 @@ public:
|
|
|
RE const & regex = regex_cache_.try_emplace(cons.regex, cons.regex).first->second;
|
|
|
std::string const str = document_.as_string();
|
|
|
if (regex.search(str)) {
|
|
|
- return note(Status::Accept, "'", str, "' matches pattern /", cons.regex, "/");
|
|
|
+ return result(Status::Accept, "'", str, "' matches pattern /", cons.regex, "/");
|
|
|
}
|
|
|
- return note(Status::Reject, "'", str, "' does not match pattern /", cons.regex, "/");
|
|
|
+ return result(Status::Reject, "'", str, "' does not match pattern /", cons.regex, "/");
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::FormatConstraint const & cons) const {
|
|
|
@@ -251,12 +250,12 @@ public:
|
|
|
NOOP_UNLESS_TYPE(String);
|
|
|
|
|
|
// TODO(samjaffe): annotate(cons.format)
|
|
|
- annotate("format '", cons.format, "'");
|
|
|
+ annotate(cons.format);
|
|
|
if (not cfg_.validate_format && not cons.is_assertion) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- return note(Status::Reject, " is unimplemented");
|
|
|
+ return result(Status::Reject, " is unimplemented");
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::AdditionalItemsConstraint const & cons) const {
|
|
|
@@ -287,10 +286,10 @@ public:
|
|
|
}
|
|
|
|
|
|
if (matches < minimum) {
|
|
|
- return note(Status::Reject, "array contains <", minimum, " matching elements");
|
|
|
+ return result(Status::Reject, "array contains <", minimum, " matching elements");
|
|
|
}
|
|
|
if (matches > maximum) {
|
|
|
- return note(Status::Reject, "array contains >", maximum, " matching elements");
|
|
|
+ return result(Status::Reject, "array contains >", maximum, " matching elements");
|
|
|
}
|
|
|
return Status::Accept;
|
|
|
}
|
|
|
@@ -298,18 +297,18 @@ public:
|
|
|
Status visit(constraint::MaxItemsConstraint const & cons) const {
|
|
|
NOOP_UNLESS_TYPE(Array);
|
|
|
if (size_t size = document_.array_size(); size > cons.value) {
|
|
|
- return note(Status::Reject, "array of size ", size, " is >", cons.value);
|
|
|
+ return result(Status::Reject, "array of size ", size, " is >", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, "array of size ", size, " is <=", cons.value);
|
|
|
+ return result(Status::Accept, "array of size ", size, " is <=", cons.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::MinItemsConstraint const & cons) const {
|
|
|
NOOP_UNLESS_TYPE(Array);
|
|
|
if (size_t size = document_.array_size(); size < cons.value) {
|
|
|
- return note(Status::Reject, "array of size ", size, " is <", cons.value);
|
|
|
+ return result(Status::Reject, "array of size ", size, " is <", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, "array of size ", size, " is >=", cons.value);
|
|
|
+ return result(Status::Accept, "array of size ", size, " is >=", cons.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -335,7 +334,7 @@ public:
|
|
|
std::map<A, size_t> cache;
|
|
|
for (auto const & [index, elem] : detail::enumerate(document_.as_array())) {
|
|
|
if (auto [it, created] = cache.emplace(elem, index); not created) {
|
|
|
- return note(Status::Reject, "items ", it->second, " and ", index, " are equal");
|
|
|
+ return result(Status::Reject, "items ", it->second, " and ", index, " are equal");
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
@@ -343,13 +342,13 @@ public:
|
|
|
for (size_t i = 0; i < array.size(); ++i) {
|
|
|
for (size_t j = i + 1; j < array.size(); ++j) {
|
|
|
if (array[i].equals(array[j], true)) {
|
|
|
- return note(Status::Reject, "items ", i, " and ", j, " are equal");
|
|
|
+ return result(Status::Reject, "items ", i, " and ", j, " are equal");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return note(Status::Accept, "all array items are unique");
|
|
|
+ return result(Status::Accept, "all array items are unique");
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::AdditionalPropertiesConstraint const & cons) const {
|
|
|
@@ -409,18 +408,18 @@ public:
|
|
|
Status visit(constraint::MaxPropertiesConstraint const & cons) const {
|
|
|
NOOP_UNLESS_TYPE(Object);
|
|
|
if (size_t size = document_.object_size(); size > cons.value) {
|
|
|
- return note(Status::Reject, "object of size ", size, " is >", cons.value);
|
|
|
+ return result(Status::Reject, "object of size ", size, " is >", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, "object of size ", size, " is <=", cons.value);
|
|
|
+ return result(Status::Accept, "object of size ", size, " is <=", cons.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::MinPropertiesConstraint const & cons) const {
|
|
|
NOOP_UNLESS_TYPE(Object);
|
|
|
if (size_t size = document_.object_size(); size < cons.value) {
|
|
|
- return note(Status::Reject, "object of size ", size, " is <", cons.value);
|
|
|
+ return result(Status::Reject, "object of size ", size, " is <", cons.value);
|
|
|
} else {
|
|
|
- return note(Status::Accept, "object of size ", size, " is >=", cons.value);
|
|
|
+ return result(Status::Accept, "object of size ", size, " is >=", cons.value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -487,10 +486,10 @@ public:
|
|
|
}
|
|
|
|
|
|
if (required.empty()) {
|
|
|
- return note(Status::Accept, "contains all required properties ", cons.properties);
|
|
|
+ return result(Status::Accept, "contains all required properties ", cons.properties);
|
|
|
}
|
|
|
|
|
|
- return note(Status::Reject, "missing required properties ", required);
|
|
|
+ return result(Status::Reject, "missing required properties ", required);
|
|
|
}
|
|
|
|
|
|
Status visit(constraint::UnevaluatedItemsConstraint const & cons) const {
|
|
|
@@ -530,7 +529,9 @@ public:
|
|
|
|
|
|
Status validate() {
|
|
|
if (std::optional<std::string> const & reject = schema_->rejects_all()) {
|
|
|
- annotate(*reject);
|
|
|
+ if (should_annotate(Status::Reject) && result_) {
|
|
|
+ result_->error(where_, schema_path_, "", *reject);
|
|
|
+ }
|
|
|
return Status::Reject;
|
|
|
}
|
|
|
|
|
|
@@ -566,36 +567,55 @@ public:
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
- template <typename... Args> void annotate(Args &&... args) const {
|
|
|
- if (not result_) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ template <typename S>
|
|
|
+ requires(std::is_constructible_v<std::string, S>) static std::string fmt(S const & str) {
|
|
|
+ return str;
|
|
|
+ }
|
|
|
+
|
|
|
+ static std::string fmt(auto const &... args) {
|
|
|
std::stringstream ss;
|
|
|
using ::jvalidate::operator<<;
|
|
|
- [[maybe_unused]] int _[] = {(ss << std::forward<Args>(args), 0)...};
|
|
|
- result_->annotate(where_, schema_path_, ss.str());
|
|
|
+ [[maybe_unused]] int _[] = {(ss << args, 0)...};
|
|
|
+ return ss.str();
|
|
|
+ }
|
|
|
+
|
|
|
+ static std::vector<std::string> fmtlist(auto const & arg) {
|
|
|
+ std::vector<std::string> strs;
|
|
|
+ for (auto const & elem : arg) {
|
|
|
+ strs.push_back(fmt(elem));
|
|
|
+ }
|
|
|
+ return strs;
|
|
|
}
|
|
|
|
|
|
- template <typename... Args> Status note(Status stat, Args &&... args) const {
|
|
|
+ bool should_annotate(Status stat) const {
|
|
|
switch (tracking_) {
|
|
|
case StoreResults::ForAnything:
|
|
|
- if (stat != Status::Noop) {
|
|
|
- annotate(std::forward<Args>(args)...);
|
|
|
- }
|
|
|
- break;
|
|
|
+ return stat != Status::Noop;
|
|
|
case StoreResults::ForValid:
|
|
|
- if (stat == Status::Accept) {
|
|
|
- annotate(std::forward<Args>(args)...);
|
|
|
- }
|
|
|
- break;
|
|
|
+ return stat == Status::Accept;
|
|
|
case StoreResults::ForInvalid:
|
|
|
- if (stat == Status::Reject) {
|
|
|
- annotate(std::forward<Args>(args)...);
|
|
|
- }
|
|
|
- break;
|
|
|
+ return stat == Status::Reject;
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+#define ANNOTATION_HELPER(name, ADD, FMT) \
|
|
|
+ void name(auto const &... args) const { \
|
|
|
+ if (not result_) { \
|
|
|
+ return; \
|
|
|
+ } \
|
|
|
+ if (schema_path_.empty()) { \
|
|
|
+ result_->ADD(where_, schema_path_, "", FMT(args...)); \
|
|
|
+ } else { \
|
|
|
+ result_->ADD(where_, schema_path_.parent(), schema_path_.back(), FMT(args...)); \
|
|
|
+ } \
|
|
|
+ }
|
|
|
+
|
|
|
+ ANNOTATION_HELPER(error, error, fmt)
|
|
|
+ ANNOTATION_HELPER(annotate, annotate, fmt)
|
|
|
+ ANNOTATION_HELPER(annotate_list, annotate, fmtlist)
|
|
|
|
|
|
- return stat;
|
|
|
+ Status result(Status stat, auto const &... args) const {
|
|
|
+ return (should_annotate(stat) ? error(args...) : void(), stat);
|
|
|
}
|
|
|
|
|
|
template <typename C> static void merge_visited(C & to, C const & from) {
|
|
|
@@ -645,7 +665,7 @@ private:
|
|
|
VISITED(K).insert(key);
|
|
|
}
|
|
|
if (status == Status::Reject and result_) {
|
|
|
- result_->annotate(std::move(result));
|
|
|
+ result_->merge(std::move(result));
|
|
|
}
|
|
|
return status;
|
|
|
}
|