validation_visitor.h 11 KB

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