validation_visitor.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. #pragma once
  2. #include <unordered_map>
  3. #include <jvalidate/constraint/array_constraint.h>
  4. #include <jvalidate/constraint/general_constraint.h>
  5. #include <jvalidate/constraint/number_constraint.h>
  6. #include <jvalidate/constraint/object_constraint.h>
  7. #include <jvalidate/constraint/string_constraint.h>
  8. #include <jvalidate/constraint/visitor.h>
  9. #include <jvalidate/detail/expect.h>
  10. #include <jvalidate/forward.h>
  11. #include <jvalidate/schema.h>
  12. #include <jvalidate/status.h>
  13. #include <jvalidate/validation_result.h>
  14. #define NOOP_UNLESS_TYPE(etype) \
  15. RETURN_UNLESS(document_.type() == adapter::Type::etype, Status::Noop)
  16. namespace jvalidate {
  17. template <Adapter A, RegexEngine RE>
  18. class ValidationVisitor : public constraint::ConstraintVisitor {
  19. private:
  20. A document_;
  21. detail::Pointer where_;
  22. schema::Node const & schema_;
  23. ValidationResult * result_;
  24. ValidationResult * local_result_;
  25. std::unordered_map<std::string, RE> & regex_cache_;
  26. public:
  27. ValidationVisitor(A const & json, schema::Node const & schema,
  28. std::unordered_map<std::string, RE> & regex_cache, ValidationResult * result)
  29. : ValidationVisitor(json, schema, {}, regex_cache, result) {}
  30. Status visit(constraint::TypeConstraint const & cons) const {
  31. return cons.types.contains(document_.type());
  32. }
  33. Status visit(constraint::ExtensionConstraint const & cons) const {
  34. return cons.validate(document_, where_, result_);
  35. }
  36. Status visit(constraint::EnumConstraint const & cons) const {
  37. for (auto const & option : cons.enumeration) {
  38. // TODO(samjaffe): Strictness
  39. if (option->apply([this](auto const & frozen) { return document_.equals(frozen); })) {
  40. return Status::Accept;
  41. }
  42. }
  43. return Status::Reject;
  44. }
  45. Status visit(constraint::AllOfConstraint const & cons) const {
  46. Status rval = Status::Accept;
  47. for (schema::Node const * subschema : cons.children) {
  48. rval &= validate_subschema(subschema);
  49. if (!rval && result_ == nullptr) {
  50. break;
  51. }
  52. }
  53. return rval;
  54. }
  55. Status visit(constraint::AnyOfConstraint const & cons) const {
  56. for (schema::Node const * subschema : cons.children) {
  57. if (validate_subschema(subschema)) {
  58. return Status::Accept;
  59. }
  60. }
  61. return Status::Reject;
  62. }
  63. Status visit(constraint::OneOfConstraint const & cons) const {
  64. size_t matches = 0;
  65. for (schema::Node const * subschema : cons.children) {
  66. if (validate_subschema(subschema)) {
  67. ++matches;
  68. }
  69. }
  70. return matches == 1 ? Status::Accept : Status::Reject;
  71. }
  72. Status visit(constraint::NotConstraint const & cons) const {
  73. return !validate_subschema(cons.child);
  74. }
  75. Status visit(constraint::ConditionalConstraint const & cons) const {
  76. if (validate_subschema(cons.if_constraint)) {
  77. return validate_subschema(cons.then_constraint);
  78. }
  79. return validate_subschema(cons.else_constraint);
  80. }
  81. Status visit(constraint::MaximumConstraint const & cons) const {
  82. switch (document_.type()) {
  83. case adapter::Type::Integer:
  84. return cons(document_.as_integer());
  85. case adapter::Type::Number:
  86. return cons(document_.as_number());
  87. default:
  88. return Status::Noop;
  89. }
  90. }
  91. Status visit(constraint::MinimumConstraint const & cons) const {
  92. switch (document_.type()) {
  93. case adapter::Type::Integer:
  94. return cons(document_.as_integer());
  95. case adapter::Type::Number:
  96. return cons(document_.as_number());
  97. default:
  98. return Status::Noop;
  99. }
  100. }
  101. Status visit(constraint::MultipleOfConstraint const & cons) const {
  102. NOOP_UNLESS_TYPE(Integer);
  103. return cons(document_.as_integer());
  104. }
  105. Status visit(constraint::MaxLengthConstraint const & cons) const {
  106. NOOP_UNLESS_TYPE(String);
  107. return cons(document_.as_string());
  108. }
  109. Status visit(constraint::MinLengthConstraint const & cons) const {
  110. NOOP_UNLESS_TYPE(String);
  111. return cons(document_.as_string());
  112. }
  113. Status visit(constraint::PatternConstraint const & cons) const {
  114. NOOP_UNLESS_TYPE(String);
  115. RE const & regex = regex_cache_.try_emplace(cons.regex, cons.regex).first->second;
  116. return regex.search(document_.as_string());
  117. }
  118. Status visit(constraint::AdditionalItemsConstraint const & cons) const {
  119. NOOP_UNLESS_TYPE(Array);
  120. auto array = document_.as_array();
  121. Status rval = Status::Accept;
  122. for (size_t i = cons.applies_after_nth; i < array.size(); ++i) {
  123. rval &= validate_subschema_on(cons.subschema, array[i], i);
  124. if (!rval && result_ == nullptr) {
  125. break;
  126. }
  127. }
  128. return rval;
  129. }
  130. Status visit(constraint::ContainsConstraint const & cons) const {
  131. NOOP_UNLESS_TYPE(Array);
  132. auto array = document_.as_array();
  133. size_t const minimum = cons.minimum.value_or(1);
  134. size_t const maximum = cons.maximum.value_or(array.size());
  135. size_t matches = 0;
  136. for (size_t i = 0; i < array.size(); ++i) {
  137. if (validate_subschema_on(cons.subschema, array[i], i)) {
  138. ++matches;
  139. }
  140. }
  141. if (matches < minimum) {
  142. return Status::Reject;
  143. }
  144. if (matches > maximum) {
  145. return Status::Reject;
  146. }
  147. return Status::Accept;
  148. }
  149. Status visit(constraint::MaxItemsConstraint const & cons) const {
  150. NOOP_UNLESS_TYPE(Array);
  151. return cons(document_.as_array());
  152. }
  153. Status visit(constraint::MinItemsConstraint const & cons) const {
  154. NOOP_UNLESS_TYPE(Array);
  155. return cons(document_.as_array());
  156. }
  157. Status visit(constraint::TupleConstraint const & cons) const {
  158. NOOP_UNLESS_TYPE(Array);
  159. auto array = document_.as_array();
  160. if (array.size() < cons.items.size()) {
  161. return Status::Reject;
  162. }
  163. Status rval = Status::Accept;
  164. for (size_t i = 0; i < cons.items.size(); ++i) {
  165. rval &= validate_subschema_on(cons.items[i], array[i], i);
  166. if (!rval && result_ == nullptr) {
  167. break;
  168. }
  169. }
  170. return rval;
  171. }
  172. Status visit(constraint::UniqueItemsConstraint const & cons) const {
  173. NOOP_UNLESS_TYPE(Array);
  174. if constexpr (std::totally_ordered<A>) {
  175. std::set<A> cache;
  176. for (A const & elem : document_.as_array()) {
  177. if (not cache.insert(elem).second) {
  178. return Status::Reject;
  179. }
  180. }
  181. } else {
  182. auto array = document_.as_array();
  183. for (size_t i = 0; i < array.size(); ++i) {
  184. for (size_t j = i + 1; j < array.size(); ++j) {
  185. if (array[i].equals(array[j], true)) {
  186. return Status::Reject;
  187. }
  188. }
  189. }
  190. }
  191. return Status::Accept;
  192. }
  193. Status visit(constraint::AdditionalPropertiesConstraint const & cons) const {
  194. NOOP_UNLESS_TYPE(Object);
  195. auto matches_any_pattern = [this, &cons](std::string const & key) {
  196. for (auto & pattern : cons.patterns) {
  197. RE const & regex = regex_cache_.try_emplace(pattern, pattern).first->second;
  198. if (regex.search(key)) {
  199. return true;
  200. }
  201. }
  202. return false;
  203. };
  204. Status rval = Status::Accept;
  205. for (auto const & [key, elem] : document_.as_object()) {
  206. if (not cons.properties.contains(key) && not matches_any_pattern(key)) {
  207. rval &= validate_subschema_on(cons.subschema, elem, key);
  208. }
  209. if (!rval && result_ == nullptr) {
  210. break;
  211. }
  212. }
  213. return rval;
  214. }
  215. Status visit(constraint::DependenciesConstraint const & cons) const {
  216. NOOP_UNLESS_TYPE(Object);
  217. auto object = document_.as_object();
  218. Status rval = Status::Accept;
  219. for (auto const & [key, subschema] : cons.subschemas) {
  220. if (not object.contains(key)) {
  221. continue;
  222. }
  223. rval &= validate_subschema(subschema);
  224. if (!rval && result_ == nullptr) {
  225. break;
  226. }
  227. }
  228. for (auto [key, required] : cons.required) {
  229. if (not object.contains(key)) {
  230. continue;
  231. }
  232. for (auto const & [key, _] : object) {
  233. required.erase(key);
  234. }
  235. rval &= required.empty();
  236. if (!rval && result_ == nullptr) {
  237. break;
  238. }
  239. }
  240. return rval;
  241. }
  242. Status visit(constraint::MaxPropertiesConstraint const & cons) const {
  243. NOOP_UNLESS_TYPE(Object);
  244. return cons(document_.as_object());
  245. }
  246. Status visit(constraint::MinPropertiesConstraint const & cons) const {
  247. NOOP_UNLESS_TYPE(Object);
  248. return cons(document_.as_object());
  249. }
  250. Status visit(constraint::PatternPropertiesConstraint const & cons) const {
  251. NOOP_UNLESS_TYPE(Object);
  252. Status rval = Status::Accept;
  253. for (auto const & [pattern, subschema] : cons.properties) {
  254. RE const & regex = regex_cache_.try_emplace(pattern, pattern).first->second;
  255. for (auto const & [key, elem] : document_.as_object()) {
  256. if (regex.search(key)) {
  257. rval &= validate_subschema_on(subschema, elem, key);
  258. }
  259. if (!rval && result_ == nullptr) {
  260. break;
  261. }
  262. }
  263. }
  264. return rval;
  265. }
  266. Status visit(constraint::PropertiesConstraint const & cons) const {
  267. NOOP_UNLESS_TYPE(Object);
  268. Status rval = Status::Accept;
  269. for (auto const & [key, elem] : document_.as_object()) {
  270. if (auto it = cons.properties.find(key); it != cons.properties.end()) {
  271. rval &= validate_subschema_on(it->second, elem, key);
  272. }
  273. if (!rval && result_ == nullptr) {
  274. break;
  275. }
  276. }
  277. return rval;
  278. }
  279. Status visit(constraint::PropertyNamesConstraint const & cons) const {
  280. NOOP_UNLESS_TYPE(Object);
  281. Status rval = Status::Accept;
  282. for (auto const & [key, _] : document_.as_object()) {
  283. // TODO(samjaffe): Should we prefer a std::string adapter like valijson?
  284. typename A::value_type key_json{key};
  285. rval &= validate_subschema_on(cons.key_schema, A(key_json), "$$key");
  286. }
  287. }
  288. Status visit(constraint::RequiredConstraint const & cons) const {
  289. NOOP_UNLESS_TYPE(Object);
  290. auto required = cons.properties;
  291. for (auto const & [key, _] : document_.as_object()) {
  292. required.erase(key);
  293. }
  294. return required.empty();
  295. }
  296. Status visit(constraint::UnevaluatedItemsConstraint const & cons) const {
  297. EXPECT_M(local_result_, "Invalid State - no result object for post-constraint");
  298. NOOP_UNLESS_TYPE(Array);
  299. Status rval = Status::Accept;
  300. auto array = document_.as_array();
  301. for (size_t i = 0; i < array.size(); ++i) {
  302. if (not local_result_->visited_items.contains(i)) {
  303. rval &= validate_subschema_on(cons.subschema, array[i], i);
  304. }
  305. if (!rval && result_ == nullptr) {
  306. break;
  307. }
  308. }
  309. }
  310. Status visit(constraint::UnevaluatedPropertiesConstraint const & cons) const {
  311. EXPECT_M(local_result_, "Invalid State - no result object for post-constraint");
  312. NOOP_UNLESS_TYPE(Object);
  313. Status rval = Status::Accept;
  314. for (auto const & [key, elem] : document_.as_object()) {
  315. if (not local_result_->visited_properties.contains(key)) {
  316. rval &= validate_subschema_on(cons.subschema, elem, key);
  317. }
  318. if (!rval && result_ == nullptr) {
  319. break;
  320. }
  321. }
  322. }
  323. Status validate() const {
  324. if (schema_.rejects_all()) {
  325. return Status::Reject;
  326. }
  327. ValidationResult local_result;
  328. if (schema_.requires_result_context() && not local_result_) {
  329. // Ensure that we store results even if there aren't any...
  330. local_result_ = &local_result;
  331. }
  332. Status rval = Status::Noop;
  333. if (auto ref = schema_.reference_schema()) {
  334. rval = validate_subschema(*ref);
  335. }
  336. for (auto const & [key, p_constraint] : schema_.constraints()) {
  337. if (rval || result_) {
  338. rval &= p_constraint->accept(*this);
  339. }
  340. }
  341. for (auto const & [key, p_constraint] : schema_.post_constraints()) {
  342. if (rval || result_) {
  343. rval &= p_constraint->accept(*this);
  344. }
  345. }
  346. return rval;
  347. }
  348. private:
  349. ValidationVisitor(A const & json, detail::Pointer const & where, schema::Node const & schema,
  350. std::unordered_map<std::string, RE> & regex_cache, ValidationResult * result,
  351. ValidationResult * local_result = nullptr)
  352. : document_(json), where_(where), schema_(schema), regex_cache_(regex_cache), result_(result),
  353. local_result_(local_result ?: result_) {}
  354. Status validate_subschema(schema::Node const * subschema) const {
  355. return CRTP(document_, *subschema, where_, regex_cache_, result_, local_result_).validate();
  356. }
  357. template <typename K>
  358. Status validate_subschema_on(schema::Node const * subschema, A const & document,
  359. K const & key) const {
  360. ValidationResult next;
  361. ValidationResult * pnext = result_ ? &next : nullptr;
  362. auto status = CRTP(document, *subschema, where_ / key, regex_cache_, pnext).validate();
  363. if (status != Status::Noop and local_result_) {
  364. local_result_->record(key);
  365. }
  366. return status;
  367. }
  368. };
  369. }