validation_visitor.h 12 KB

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