validator.h 4.9 KB

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