validation_visitor.h 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. #pragma once
  2. #include <tuple>
  3. #include <type_traits>
  4. #include <unordered_map>
  5. #include <vector>
  6. #include <jvalidate/compat/enumerate.h>
  7. #include <jvalidate/constraint/array_constraint.h>
  8. #include <jvalidate/constraint/general_constraint.h>
  9. #include <jvalidate/constraint/number_constraint.h>
  10. #include <jvalidate/constraint/object_constraint.h>
  11. #include <jvalidate/constraint/string_constraint.h>
  12. #include <jvalidate/constraint/visitor.h>
  13. #include <jvalidate/detail/expect.h>
  14. #include <jvalidate/detail/iostream.h>
  15. #include <jvalidate/detail/number.h>
  16. #include <jvalidate/detail/pointer.h>
  17. #include <jvalidate/detail/scoped_state.h>
  18. #include <jvalidate/detail/string_adapter.h>
  19. #include <jvalidate/forward.h>
  20. #include <jvalidate/schema.h>
  21. #include <jvalidate/status.h>
  22. #include <jvalidate/validation_config.h>
  23. #include <jvalidate/validation_result.h>
  24. #define VISITED(type) std::get<std::unordered_set<type>>(*visited_)
  25. #define VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT(subschema, subinstance, path, local_visited) \
  26. do { \
  27. Status const partial = validate_subschema_on(subschema, subinstance, path); \
  28. rval &= partial; \
  29. if (result_ and partial != Status::Noop) { \
  30. local_visited.insert(local_visited.end(), path); \
  31. } \
  32. } while (false)
  33. #define NOOP_UNLESS_TYPE(etype) \
  34. RETURN_UNLESS(adapter::Type::etype == document_.type(), Status::Noop)
  35. #define BREAK_EARLY_IF_NO_RESULT_TREE() \
  36. do { \
  37. if (rval == Status::Reject and not result_ and not visited_) { \
  38. break; \
  39. } \
  40. } while (false)
  41. namespace jvalidate {
  42. JVALIDATE_TRIBOOL_TYPE(StoreResults, ForValid, ForInvalid, ForAnything);
  43. template <Adapter A, RegexEngine RE>
  44. class ValidationVisitor : public constraint::ConstraintVisitor {
  45. private:
  46. template <Adapter, RegexEngine> friend class ValidationVisitor;
  47. using VisitedAnnotation = std::tuple<std::unordered_set<size_t>, std::unordered_set<std::string>>;
  48. private:
  49. A document_;
  50. detail::Pointer where_;
  51. detail::Pointer schema_path_;
  52. schema::Node const * schema_;
  53. ValidationResult * result_;
  54. ValidationConfig const & cfg_;
  55. std::unordered_map<std::string, RE> & regex_cache_;
  56. mutable VisitedAnnotation * visited_ = nullptr;
  57. mutable StoreResults tracking_ = StoreResults::ForInvalid;
  58. public:
  59. /**
  60. * @brief Construct a new ValidationVisitor
  61. *
  62. * @param json The JSON document instance being validated
  63. * @param schema The parsed JSON Schema
  64. * @param cfg General configuration settings for how the run is executed
  65. * @param regex_cache A cache of string regular expressions to compiled
  66. * regular expressions
  67. * @param[optional] result A cache of result/annotation info for the user to
  68. * receive a detailed summary of why a document is supported/unsupported.
  69. */
  70. ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
  71. std::unordered_map<std::string, RE> & regex_cache, ValidationResult * result)
  72. : document_(json), schema_(&schema), result_(result), cfg_(cfg), regex_cache_(regex_cache) {}
  73. Status visit(constraint::TypeConstraint const & cons) const {
  74. adapter::Type const type = document_.type();
  75. for (adapter::Type const accept : cons.types) {
  76. if (type == accept) {
  77. return result(Status::Accept, type, " is in types [", cons.types, "]");
  78. }
  79. if (accept == adapter::Type::Number && type == adapter::Type::Integer) {
  80. return result(Status::Accept, type, " is in types [", cons.types, "]");
  81. }
  82. if (accept == adapter::Type::Integer && type == adapter::Type::Number &&
  83. detail::is_json_integer(document_.as_number())) {
  84. return result(Status::Accept, type, " is in types [", cons.types, "]");
  85. }
  86. }
  87. return result(Status::Reject, type, " is not in types [", cons.types, "]");
  88. }
  89. Status visit(constraint::ExtensionConstraint const & cons) const {
  90. return cons.validate(document_, where_, result_);
  91. }
  92. Status visit(constraint::EnumConstraint const & cons) const {
  93. auto is_equal = [this](auto const & frozen) {
  94. return document_.equals(frozen, cfg_.strict_equality);
  95. };
  96. for (auto const & [index, option] : detail::enumerate(cons.enumeration)) {
  97. if (option->apply(is_equal)) {
  98. return result(Status::Accept, index);
  99. }
  100. }
  101. return Status::Reject;
  102. }
  103. Status visit(constraint::AllOfConstraint const & cons) const {
  104. Status rval = Status::Accept;
  105. std::set<size_t> unmatched;
  106. for (auto const & [index, subschema] : detail::enumerate(cons.children)) {
  107. if (auto stat = validate_subschema(subschema, index); stat == Status::Reject) {
  108. rval = Status::Reject;
  109. unmatched.insert(index);
  110. }
  111. BREAK_EARLY_IF_NO_RESULT_TREE();
  112. }
  113. if (rval == Status::Reject) {
  114. return result(rval, "does not validate subschemas ", unmatched);
  115. }
  116. return result(rval, "validates all subschemas");
  117. }
  118. Status visit(constraint::AnyOfConstraint const & cons) const {
  119. std::optional<size_t> first_validated;
  120. for (auto const & [index, subschema] : detail::enumerate(cons.children)) {
  121. if (validate_subschema(subschema, index)) {
  122. first_validated = index;
  123. }
  124. if (not visited_ && first_validated.has_value()) {
  125. break;
  126. }
  127. }
  128. if (first_validated.has_value()) {
  129. return result(Status::Accept, "validates subschema ", *first_validated);
  130. }
  131. return result(Status::Reject, "validates none of the subschemas");
  132. }
  133. Status visit(constraint::OneOfConstraint const & cons) const {
  134. std::set<size_t> matches;
  135. for (auto const & [index, subschema] : detail::enumerate(cons.children)) {
  136. scoped_state(tracking_, StoreResults::ForAnything);
  137. if (validate_subschema(subschema, index)) {
  138. matches.insert(index);
  139. }
  140. }
  141. if (matches.size() == 1) {
  142. return result(Status::Accept, "validates subschema ", *matches.begin());
  143. }
  144. return result(Status::Reject, "validates multiple subschemas ", matches);
  145. }
  146. Status visit(constraint::NotConstraint const & cons) const {
  147. scoped_state(visited_, nullptr);
  148. scoped_state(tracking_, !tracking_);
  149. bool const rejected = validate_subschema(cons.child) == Status::Reject;
  150. return rejected;
  151. }
  152. Status visit(constraint::ConditionalConstraint const & cons) const {
  153. Status const if_true = [this, &cons]() {
  154. scoped_state(tracking_, StoreResults::ForAnything);
  155. return validate_subschema(cons.if_constraint);
  156. }();
  157. annotate(if_true ? "valid" : "invalid");
  158. if (if_true) {
  159. return validate_subschema(cons.then_constraint, detail::parent, "then");
  160. }
  161. return validate_subschema(cons.else_constraint, detail::parent, "else");
  162. }
  163. Status visit(constraint::MaximumConstraint const & cons) const {
  164. switch (document_.type()) {
  165. case adapter::Type::Integer:
  166. if (int64_t value = document_.as_integer(); not cons(value)) {
  167. return result(Status::Reject, value, cons.exclusive ? " >= " : " > ", cons.value);
  168. } else {
  169. return result(Status::Accept, value, cons.exclusive ? " < " : " <= ", cons.value);
  170. }
  171. case adapter::Type::Number:
  172. if (double value = document_.as_number(); not cons(value)) {
  173. return result(Status::Reject, value, cons.exclusive ? " >= " : " > ", cons.value);
  174. } else {
  175. return result(Status::Accept, value, cons.exclusive ? " < " : " <= ", cons.value);
  176. }
  177. default:
  178. return Status::Noop;
  179. }
  180. }
  181. Status visit(constraint::MinimumConstraint const & cons) const {
  182. switch (document_.type()) {
  183. case adapter::Type::Integer:
  184. if (int64_t value = document_.as_integer(); not cons(value)) {
  185. return result(Status::Reject, value, cons.exclusive ? " <= " : " < ", cons.value);
  186. } else {
  187. return result(Status::Accept, value, cons.exclusive ? " > " : " >= ", cons.value);
  188. }
  189. case adapter::Type::Number:
  190. if (double value = document_.as_number(); not cons(value)) {
  191. return result(Status::Reject, value, cons.exclusive ? " <= " : " < ", cons.value);
  192. } else {
  193. return result(Status::Accept, value, cons.exclusive ? " > " : " >= ", cons.value);
  194. }
  195. default:
  196. return Status::Noop;
  197. }
  198. }
  199. Status visit(constraint::MultipleOfConstraint const & cons) const {
  200. adapter::Type const type = document_.type();
  201. RETURN_UNLESS(type == adapter::Type::Number || type == adapter::Type::Integer, Status::Noop);
  202. if (double value = document_.as_number(); not cons(value)) {
  203. return result(Status::Reject, value, " is not a multiple of ", cons.value);
  204. } else {
  205. return result(Status::Accept, value, " is a multiple of ", cons.value);
  206. }
  207. }
  208. Status visit(constraint::MaxLengthConstraint const & cons) const {
  209. NOOP_UNLESS_TYPE(String);
  210. std::string const str = document_.as_string();
  211. if (int64_t len = detail::length(str); len > cons.value) {
  212. return result(Status::Reject, "string of length ", len, " is >", cons.value);
  213. } else {
  214. return result(Status::Accept, "string of length ", len, " is <=", cons.value);
  215. }
  216. }
  217. Status visit(constraint::MinLengthConstraint const & cons) const {
  218. NOOP_UNLESS_TYPE(String);
  219. std::string const str = document_.as_string();
  220. if (int64_t len = detail::length(str); len < cons.value) {
  221. return result(Status::Reject, "string of length ", len, " is <", cons.value);
  222. } else {
  223. return result(Status::Accept, "string of length ", len, " is >=", cons.value);
  224. }
  225. }
  226. Status visit(constraint::PatternConstraint const & cons) const {
  227. NOOP_UNLESS_TYPE(String);
  228. RE const & regex = regex_cache_.try_emplace(cons.regex, cons.regex).first->second;
  229. std::string const str = document_.as_string();
  230. if (regex.search(str)) {
  231. return result(Status::Accept, "string matches pattern /", cons.regex, "/");
  232. }
  233. return result(Status::Reject, "string does not match pattern /", cons.regex, "/");
  234. }
  235. Status visit(constraint::FormatConstraint const & cons) const {
  236. // https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#name-defined-formats
  237. NOOP_UNLESS_TYPE(String);
  238. annotate(cons.format);
  239. if (not cfg_.validate_format && not cons.is_assertion) {
  240. return true;
  241. }
  242. return result(Status::Reject, " is unimplemented");
  243. }
  244. Status visit(constraint::AdditionalItemsConstraint const & cons) const {
  245. NOOP_UNLESS_TYPE(Array);
  246. auto array = document_.as_array();
  247. Status rval = Status::Accept;
  248. std::vector<size_t> items;
  249. for (size_t i = cons.applies_after_nth; i < array.size(); ++i) {
  250. VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT(cons.subschema, array[i], i, items);
  251. BREAK_EARLY_IF_NO_RESULT_TREE();
  252. }
  253. annotate_list(items);
  254. return rval;
  255. }
  256. Status visit(constraint::ContainsConstraint const & cons) const {
  257. NOOP_UNLESS_TYPE(Array);
  258. auto array = document_.as_array();
  259. size_t const minimum = cons.minimum.value_or(1);
  260. size_t const maximum = cons.maximum.value_or(array.size());
  261. size_t matches = 0;
  262. for (size_t i = 0; i < array.size(); ++i) {
  263. if (validate_subschema_on(cons.subschema, array[i], i)) {
  264. ++matches;
  265. }
  266. }
  267. if (matches < minimum) {
  268. return result(Status::Reject, "array contains <", minimum, " matching items");
  269. }
  270. if (matches > maximum) {
  271. return result(Status::Reject, "array contains >", maximum, " matching items");
  272. }
  273. return result(Status::Accept, "array contains ", matches, " matching items");
  274. }
  275. Status visit(constraint::MaxItemsConstraint const & cons) const {
  276. NOOP_UNLESS_TYPE(Array);
  277. if (size_t size = document_.array_size(); size > cons.value) {
  278. return result(Status::Reject, "array of size ", size, " is >", cons.value);
  279. } else {
  280. return result(Status::Accept, "array of size ", size, " is <=", cons.value);
  281. }
  282. }
  283. Status visit(constraint::MinItemsConstraint const & cons) const {
  284. NOOP_UNLESS_TYPE(Array);
  285. if (size_t size = document_.array_size(); size < cons.value) {
  286. return result(Status::Reject, "array of size ", size, " is <", cons.value);
  287. } else {
  288. return result(Status::Accept, "array of size ", size, " is >=", cons.value);
  289. }
  290. }
  291. Status visit(constraint::TupleConstraint const & cons) const {
  292. NOOP_UNLESS_TYPE(Array);
  293. Status rval = Status::Accept;
  294. std::vector<size_t> items;
  295. for (auto const & [index, item] : detail::enumerate(document_.as_array())) {
  296. if (index >= cons.items.size()) {
  297. break;
  298. }
  299. VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT(cons.items[index], item, index, items);
  300. BREAK_EARLY_IF_NO_RESULT_TREE();
  301. }
  302. annotate_list(items);
  303. return rval;
  304. }
  305. Status visit(constraint::UniqueItemsConstraint const & cons) const {
  306. NOOP_UNLESS_TYPE(Array);
  307. if constexpr (std::totally_ordered<A>) {
  308. std::map<A, size_t> cache;
  309. for (auto const & [index, elem] : detail::enumerate(document_.as_array())) {
  310. if (auto [it, created] = cache.emplace(elem, index); not created) {
  311. return result(Status::Reject, "items ", it->second, " and ", index, " are equal");
  312. }
  313. }
  314. } else {
  315. auto array = document_.as_array();
  316. for (size_t i = 0; i < array.size(); ++i) {
  317. for (size_t j = i + 1; j < array.size(); ++j) {
  318. if (array[i].equals(array[j], true)) {
  319. return result(Status::Reject, "items ", i, " and ", j, " are equal");
  320. }
  321. }
  322. }
  323. }
  324. return result(Status::Accept, "all array items are unique");
  325. }
  326. Status visit(constraint::AdditionalPropertiesConstraint const & cons) const {
  327. NOOP_UNLESS_TYPE(Object);
  328. auto matches_any_pattern = [this, &cons](std::string const & key) {
  329. for (auto & pattern : cons.patterns) {
  330. RE const & regex = regex_cache_.try_emplace(pattern, pattern).first->second;
  331. if (regex.search(key)) {
  332. return true;
  333. }
  334. }
  335. return false;
  336. };
  337. Status rval = Status::Accept;
  338. std::vector<std::string> properties;
  339. for (auto const & [key, elem] : document_.as_object()) {
  340. if (not cons.properties.contains(key) && not matches_any_pattern(key)) {
  341. VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT(cons.subschema, elem, key, properties);
  342. }
  343. BREAK_EARLY_IF_NO_RESULT_TREE();
  344. }
  345. annotate_list(properties);
  346. return rval;
  347. }
  348. Status visit(constraint::DependenciesConstraint const & cons) const {
  349. NOOP_UNLESS_TYPE(Object);
  350. auto object = document_.as_object();
  351. Status rval = Status::Accept;
  352. for (auto const & [key, subschema] : cons.subschemas) {
  353. if (not object.contains(key)) {
  354. continue;
  355. }
  356. rval &= validate_subschema(subschema, key);
  357. BREAK_EARLY_IF_NO_RESULT_TREE();
  358. }
  359. for (auto [key, required] : cons.required) {
  360. if (not object.contains(key)) {
  361. continue;
  362. }
  363. for (auto const & [key, _] : object) {
  364. required.erase(key);
  365. }
  366. rval &= required.empty();
  367. BREAK_EARLY_IF_NO_RESULT_TREE();
  368. }
  369. return rval;
  370. }
  371. Status visit(constraint::MaxPropertiesConstraint const & cons) const {
  372. NOOP_UNLESS_TYPE(Object);
  373. if (size_t size = document_.object_size(); size > cons.value) {
  374. return result(Status::Reject, "object of size ", size, " is >", cons.value);
  375. } else {
  376. return result(Status::Accept, "object of size ", size, " is <=", cons.value);
  377. }
  378. }
  379. Status visit(constraint::MinPropertiesConstraint const & cons) const {
  380. NOOP_UNLESS_TYPE(Object);
  381. if (size_t size = document_.object_size(); size < cons.value) {
  382. return result(Status::Reject, "object of size ", size, " is <", cons.value);
  383. } else {
  384. return result(Status::Accept, "object of size ", size, " is >=", cons.value);
  385. }
  386. }
  387. Status visit(constraint::PatternPropertiesConstraint const & cons) const {
  388. NOOP_UNLESS_TYPE(Object);
  389. std::vector<std::string> properties;
  390. Status rval = Status::Accept;
  391. for (auto const & [pattern, subschema] : cons.properties) {
  392. RE const & regex = regex_cache_.try_emplace(pattern, pattern).first->second;
  393. for (auto const & [key, elem] : document_.as_object()) {
  394. if (not regex.search(key)) {
  395. continue;
  396. }
  397. VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT(subschema, elem, key, properties);
  398. BREAK_EARLY_IF_NO_RESULT_TREE();
  399. }
  400. }
  401. annotate_list(properties);
  402. return rval;
  403. }
  404. Status visit(constraint::PropertiesConstraint const & cons) const {
  405. NOOP_UNLESS_TYPE(Object);
  406. Status rval = Status::Accept;
  407. auto object = document_.as_object();
  408. if constexpr (MutableAdapter<A>) {
  409. for (auto const & [key, subschema] : cons.properties) {
  410. auto const * default_value = subschema->default_value();
  411. if (default_value && not object.contains(key)) {
  412. object.assign(key, *default_value);
  413. }
  414. }
  415. }
  416. std::vector<std::string> properties;
  417. for (auto const & [key, elem] : object) {
  418. if (auto it = cons.properties.find(key); it != cons.properties.end()) {
  419. VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT(it->second, elem, key, properties);
  420. }
  421. BREAK_EARLY_IF_NO_RESULT_TREE();
  422. }
  423. annotate_list(properties);
  424. return rval;
  425. }
  426. Status visit(constraint::PropertyNamesConstraint const & cons) const {
  427. NOOP_UNLESS_TYPE(Object);
  428. Status rval = Status::Accept;
  429. for (auto const & [key, _] : document_.as_object()) {
  430. // TODO(samjaffe): Should we prefer a std::string adapter like valijson?
  431. rval &=
  432. validate_subschema_on(cons.key_schema, detail::StringAdapter(key), std::string("$$key"));
  433. }
  434. return rval;
  435. }
  436. Status visit(constraint::RequiredConstraint const & cons) const {
  437. NOOP_UNLESS_TYPE(Object);
  438. auto required = cons.properties;
  439. for (auto const & [key, _] : document_.as_object()) {
  440. required.erase(key);
  441. }
  442. if (required.empty()) {
  443. return result(Status::Accept, "contains all required properties ", cons.properties);
  444. }
  445. return result(Status::Reject, "missing required properties ", required);
  446. }
  447. Status visit(constraint::UnevaluatedItemsConstraint const & cons) const {
  448. NOOP_UNLESS_TYPE(Array);
  449. if (not visited_) {
  450. return Status::Reject;
  451. }
  452. Status rval = Status::Accept;
  453. std::vector<size_t> items;
  454. for (auto const & [index, item] : detail::enumerate(document_.as_array())) {
  455. if (not VISITED(size_t).contains(index)) {
  456. VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT(cons.subschema, item, index, items);
  457. }
  458. BREAK_EARLY_IF_NO_RESULT_TREE();
  459. }
  460. annotate_list(items);
  461. return rval;
  462. }
  463. Status visit(constraint::UnevaluatedPropertiesConstraint const & cons) const {
  464. NOOP_UNLESS_TYPE(Object);
  465. if (not visited_) {
  466. return Status::Reject;
  467. }
  468. Status rval = Status::Accept;
  469. std::vector<std::string> properties;
  470. for (auto const & [key, elem] : document_.as_object()) {
  471. if (not VISITED(std::string).contains(key)) {
  472. VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT(cons.subschema, elem, key, properties);
  473. }
  474. BREAK_EARLY_IF_NO_RESULT_TREE();
  475. }
  476. annotate_list(properties);
  477. return rval;
  478. }
  479. /**
  480. * @brief The main entry point into the validator. Validates the provided
  481. * document according to the schema.
  482. */
  483. Status validate() {
  484. // Step 1) Check if this is an always-false schema. Sometimes, this will
  485. // have a custom message.
  486. if (std::optional<std::string> const & reject = schema_->rejects_all()) {
  487. if (should_annotate(Status::Reject)) {
  488. // This will only be run if we are interested in why something is
  489. // rejected. For example - `{ "not": false }` doesn't produce a
  490. // meaningful annotation...
  491. result_->error(where_, schema_path_, "", *reject);
  492. }
  493. // ...We do always record the result if a result object is present.
  494. (result_ ? result_->valid(where_, schema_path_, false) : void());
  495. return Status::Reject;
  496. }
  497. if (schema_->accepts_all()) {
  498. // An accept-all schema is not No-Op for the purpose of unevaluated*
  499. (result_ ? result_->valid(where_, schema_path_, true) : void());
  500. return Status::Accept;
  501. }
  502. // Begin tracking evaluations for unevaluated* keywords. The annotation
  503. // object is passed down from parent visitor to child visitor to allow all
  504. // schemas to mark whether they visited a certain item or property.
  505. VisitedAnnotation annotate;
  506. if (schema_->requires_result_context() and not visited_) {
  507. visited_ = &annotate;
  508. }
  509. Status rval = Status::Noop;
  510. // Before Draft2019_09, reference schemas could not coexist with other
  511. // constraints. This is enforced in the parsing of the schema, rather than
  512. // during validation {@see jvalidate::schema::Node::construct}.
  513. if (std::optional<schema::Node const *> ref = schema_->reference_schema()) {
  514. rval = validate_subschema(*ref, "$ref");
  515. }
  516. detail::Pointer const current_schema = schema_path_;
  517. for (auto const & [key, p_constraint] : schema_->constraints()) {
  518. BREAK_EARLY_IF_NO_RESULT_TREE();
  519. schema_path_ = current_schema / key;
  520. rval &= p_constraint->accept(*this);
  521. }
  522. // Post Constraints represent the unevaluatedItems and unevaluatedProperties
  523. // keywords.
  524. for (auto const & [key, p_constraint] : schema_->post_constraints()) {
  525. BREAK_EARLY_IF_NO_RESULT_TREE();
  526. schema_path_ = current_schema / key;
  527. rval &= p_constraint->accept(*this);
  528. }
  529. (result_ ? result_->valid(where_, current_schema, static_cast<bool>(rval)) : void());
  530. return rval;
  531. }
  532. private:
  533. template <typename S>
  534. requires(std::is_constructible_v<std::string, S>)
  535. static std::string fmt(S const & str) {
  536. return str;
  537. }
  538. static std::string fmt(auto const &... args) {
  539. std::stringstream ss;
  540. using ::jvalidate::operator<<;
  541. [[maybe_unused]] int _[] = {(ss << args, 0)...};
  542. return ss.str();
  543. }
  544. static std::vector<std::string> fmtlist(auto const & arg) {
  545. std::vector<std::string> strs;
  546. for (auto const & elem : arg) {
  547. strs.push_back(fmt(elem));
  548. }
  549. return strs;
  550. }
  551. bool should_annotate(Status stat) const {
  552. if (not result_) {
  553. return false;
  554. }
  555. switch (*tracking_) {
  556. case StoreResults::ForAnything:
  557. return stat != Status::Noop;
  558. case StoreResults::ForValid:
  559. return stat == Status::Accept;
  560. case StoreResults::ForInvalid:
  561. return stat == Status::Reject;
  562. }
  563. }
  564. #define ANNOTATION_HELPER(name, ADD, FMT) \
  565. void name(auto const &... args) const { \
  566. if (not result_) { \
  567. /* do nothing if there's no result object to append to */ \
  568. } else if (schema_path_.empty()) { \
  569. result_->ADD(where_, schema_path_, "", FMT(args...)); \
  570. } else { \
  571. result_->ADD(where_, schema_path_.parent(), schema_path_.back(), FMT(args...)); \
  572. } \
  573. }
  574. ANNOTATION_HELPER(error, error, fmt)
  575. ANNOTATION_HELPER(annotate, annotate, fmt)
  576. ANNOTATION_HELPER(annotate_list, annotate, fmtlist)
  577. Status result(Status stat, auto const &... args) const {
  578. return (should_annotate(stat) ? error(args...) : void(), stat);
  579. }
  580. /**
  581. * @brief Walking function for entering a subschema.
  582. *
  583. * @param subschema The "subschema" being validated. This is either another
  584. * schema object (jvalidate::schema::Node), or a constraint.
  585. * @param keys... The path to this subschema, relative to the current schema
  586. * evaluation.
  587. *
  588. * @return The status of validating the current instance against the
  589. * subschema.
  590. */
  591. template <typename... K>
  592. Status validate_subschema(constraint::SubConstraint const & subschema, K const &... keys) const {
  593. if (schema::Node const * const * ppschema = std::get_if<0>(&subschema)) {
  594. return validate_subschema(*ppschema, keys...);
  595. } else {
  596. return std::get<1>(subschema)->accept(*this);
  597. }
  598. }
  599. /**
  600. * @brief Walking function for entering a subschema. Creates a new validation
  601. * visitor in order to continue evaluation.
  602. *
  603. * @param subschema The subschema being validated.
  604. * @param keys... The path to this subschema, relative to the current schema
  605. * evaluation.
  606. *
  607. * @return The status of validating the current instance against the
  608. * subschema.
  609. */
  610. template <typename... K>
  611. Status validate_subschema(schema::Node const * subschema, K const &... keys) const {
  612. VisitedAnnotation annotate;
  613. ValidationVisitor next = *this;
  614. ((next.schema_path_ /= keys), ...);
  615. std::tie(next.schema_, next.visited_) =
  616. std::forward_as_tuple(subschema, visited_ ? &annotate : nullptr);
  617. Status rval = next.validate();
  618. if (rval == Status::Accept and visited_) {
  619. std::get<0>(*visited_).merge(std::get<0>(annotate));
  620. std::get<1>(*visited_).merge(std::get<1>(annotate));
  621. }
  622. return rval;
  623. }
  624. /**
  625. * @brief Walking function for entering a subschema and child document.
  626. * Creates a new validation visitor in order to continue evaluation.
  627. *
  628. * @param subschema The subschema being validated.
  629. * @param document The child document being evaluated.
  630. * @param key The path to this document instance.
  631. *
  632. * @return The status of validating the current instance against the
  633. * subschema.
  634. */
  635. template <Adapter A2, typename K>
  636. Status validate_subschema_on(schema::Node const * subschema, A2 const & document,
  637. K const & key) const {
  638. ValidationResult result;
  639. ValidationVisitor<A2, RE> next{document, *subschema, cfg_, regex_cache_,
  640. result_ ? &result : nullptr};
  641. next.where_ = where_ / key;
  642. next.schema_path_ = schema_path_;
  643. next.tracking_ = tracking_;
  644. auto status = next.validate();
  645. if (status == Status::Accept and visited_) {
  646. VISITED(K).insert(key);
  647. }
  648. if (status == Status::Reject and result_) {
  649. result_->merge(std::move(result));
  650. }
  651. return status;
  652. }
  653. };
  654. }