validator.h 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #pragma once
  2. #include <regex>
  3. #include <unordered_map>
  4. #include <jvalidate/detail/on_block_exit.h>
  5. #include <jvalidate/forward.h>
  6. #include <jvalidate/status.h>
  7. #include <jvalidate/validation_config.h>
  8. #include <jvalidate/validation_visitor.h>
  9. namespace jvalidate::detail {
  10. /**
  11. * @brief An implementation of a regular expression "engine", for use with
  12. * constraints like "pattern" and "patternProperties".
  13. * Uses std::regex as its underlying implementation.
  14. *
  15. * While being std::regex means that it is the most sensible choice for a
  16. * default RegexEngine, the performance of std::regex is generally the worst
  17. * among C++ regex utilities, and it struggles to compile several patterns.
  18. * See https://stackoverflow.com/questions/70583395/ for an explaination.
  19. *
  20. * If you need to use complicated patterns in your json schema, provide a
  21. * RegexEngine compatible wrapper for a different library, such as re2.
  22. */
  23. class StdRegexEngine {
  24. private:
  25. std::unordered_map<std::string, std::regex> cache_;
  26. public:
  27. static bool is_valid(std::string const & regex) {
  28. try {
  29. [[maybe_unused]] std::regex _{regex};
  30. return true;
  31. } catch (...) { return false; }
  32. }
  33. bool search(std::string const & regex, std::string const & text) {
  34. // TODO: detail::regex_escape
  35. auto const & re = cache_.try_emplace(regex, regex).first->second;
  36. return std::regex_search(text, re);
  37. }
  38. };
  39. /**
  40. * @brief An implementation of an "Extension Constraint Visitor" plugin that
  41. * does nothing.
  42. */
  43. struct StubExtensionVisitor {};
  44. }
  45. namespace jvalidate {
  46. /**
  47. * @brief A validator is the tool by which a JSON object is actually validated
  48. * against a schema.
  49. *
  50. * @tparam RE A type that can be used to solve regular expressions
  51. */
  52. template <RegexEngine RE = detail::StdRegexEngine,
  53. typename ExtensionVisitor = detail::StubExtensionVisitor>
  54. class Validator {
  55. private:
  56. schema::Node const & schema_;
  57. ValidationConfig cfg_;
  58. ExtensionVisitor extension_;
  59. RE regex_;
  60. public:
  61. /**
  62. * @brief Construct a Validator
  63. *
  64. * @param schema The root schema being validated against. Must outlive this.
  65. *
  66. * @param cfg Any special (runtime) configuration rules being applied to the
  67. * validator.
  68. */
  69. Validator(schema::Node const & schema, ExtensionVisitor extension = {},
  70. ValidationConfig const & cfg = {})
  71. : schema_(schema), cfg_(cfg), extension_(extension) {}
  72. Validator(schema::Node const & schema, ValidationConfig const & cfg)
  73. : schema_(schema), cfg_(cfg) {}
  74. template <typename... Args> Validator(schema::Node &&, Args &&...) = delete;
  75. /**
  76. * @brief Run validation on the given JSON
  77. *
  78. * @tparam A Any Adapter type, in principle a subclass of adapter::Adapter.
  79. * Disallows mutation via ValidationConfig.construct_default_values
  80. *
  81. * @param json The value being validated
  82. *
  83. * @param result An optional out-param of detailed information about
  84. * validation failures. If result is not provided, then the validator will
  85. * terminate on the first error. Otherwise it will run through the entire
  86. * schema to provide a record of all of the failures.
  87. */
  88. template <Adapter A>
  89. requires(not MutableAdapter<A>)
  90. bool validate(A const & json, ValidationResult * result = nullptr) {
  91. EXPECT_M(not cfg_.construct_default_values,
  92. "Cannot perform mutations on an immutable JSON Adapter");
  93. detail::OnBlockExit _ = [&result, this]() { post_process(result); };
  94. return static_cast<bool>(
  95. ValidationVisitor(schema_, cfg_, regex_, extension_, result).validate(json));
  96. }
  97. /**
  98. * @brief Run validation on the given JSON
  99. *
  100. * @tparam A Any Adapter type that supports assignment, in principle a
  101. * subclass of adapter::Adapter.
  102. *
  103. * @param json The value being validated. Because A is a reference-wrapper,
  104. * the underlying value may be mutated.
  105. *
  106. * @param result An optional out-param of detailed information about
  107. * validation failures. If result is not provided, then the validator will
  108. * terminate on the first error. Otherwise it will run through the entire
  109. * schema to provide a record of all of the failures.
  110. */
  111. template <MutableAdapter A> bool validate(A const & json, ValidationResult * result = nullptr) {
  112. detail::OnBlockExit _ = [&result, this]() { post_process(result); };
  113. return static_cast<bool>(
  114. ValidationVisitor(schema_, cfg_, regex_, extension_, result).validate(json));
  115. }
  116. /**
  117. * @brief Run validation on the given JSON
  118. *
  119. * @tparam JSON A concrete JSON type. Will be turned into an Adapter, or a
  120. * MutableAdapter (if json is non-const and exists).
  121. *
  122. * @param json The value being validated.
  123. *
  124. * @param result An optional out-param of detailed information about
  125. * validation failures. If result is not provided, then the validator will
  126. * terminate on the first error. Otherwise it will run through the entire
  127. * schema to provide a record of all of the failures.
  128. */
  129. template <typename JSON>
  130. requires(not Adapter<JSON>)
  131. bool validate(JSON & json, ValidationResult * result = nullptr) {
  132. return validate(adapter::AdapterFor<JSON>(json), result);
  133. }
  134. private:
  135. void post_process(ValidationResult *& result) const {
  136. if (result == nullptr) {
  137. return;
  138. }
  139. if (cfg_.only_return_results_with_error) {
  140. *result = result->only_errors();
  141. }
  142. }
  143. };
  144. }