vocabulary.h 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #pragma once
  2. #include <string>
  3. #include <string_view>
  4. #include <unordered_map>
  5. #include <unordered_set>
  6. #include <jvalidate/detail/expect.h>
  7. #include <jvalidate/enum.h>
  8. #include <jvalidate/forward.h>
  9. #include <jvalidate/vocabulary.h>
  10. namespace jvalidate::detail {
  11. template <Adapter A> struct ParserContext;
  12. template <Adapter A> class Vocabulary {
  13. private:
  14. schema::Version version_ = schema::Version::Draft2020_12;
  15. std::unordered_map<std::string_view, vocabulary::Metadata<A>> metadata_;
  16. std::unordered_set<std::string_view> permitted_;
  17. std::unordered_set<std::string> vocabularies_;
  18. public:
  19. Vocabulary() = default;
  20. Vocabulary(schema::Version version,
  21. std::unordered_map<std::string_view, vocabulary::Metadata<A>> metadata)
  22. : version_(version), metadata_(std::move(metadata)) {
  23. for (auto const & [keyword, _] : metadata_) {
  24. permitted_.emplace(keyword);
  25. }
  26. }
  27. /**
  28. * @brief Reset the list of keywords that Vocabulary actually respects
  29. *
  30. * @param permitted_keywords The selection of keywords to allow for
  31. * searches/constraint building. Note that a constraint might be
  32. * registered to a null function for compatibility with this.
  33. *
  34. * @param vocabularies An optional selection of vocabulary schemas, used
  35. * as metadata, and deducing {@see is_format_assertion}.
  36. */
  37. void restrict(std::unordered_map<std::string, bool> const & permitted_keywords,
  38. std::unordered_set<std::string> const & vocabularies = {}) & {
  39. permitted_.clear();
  40. vocabularies_ = vocabularies;
  41. for (auto const & [keyword, _] : metadata_) {
  42. // We only file permitted_keywords into this Vocabulary if we have defined
  43. // bindings for that keyword
  44. if (permitted_keywords.contains(std::string(keyword))) {
  45. permitted_.insert(keyword);
  46. }
  47. }
  48. for (auto const & [keyword, required] : permitted_keywords) {
  49. EXPECT_M(not required || keyword.starts_with("$") || metadata_.contains(keyword),
  50. "Vocabulary Keyword " << keyword << " required, but not implemented");
  51. }
  52. }
  53. schema::Version version() const { return version_; }
  54. bool is_format_assertion() const {
  55. // In Draft07 and prior - format assertions were considered enabled by
  56. // default. This is - of course - problematic because very few
  57. // implementations actually had full support for format constraints.
  58. if (version_ < schema::Version::Draft2019_09) {
  59. return true;
  60. }
  61. // Some implementations wouldn't even bother with format constraints, and
  62. // others would provide implementations that either missed a number of edge
  63. // cases or were flat-out wrong on certain matters.
  64. // Therefore - starting in Draft 2019-09, the format keyword is an
  65. // annotation by default, instead of an assertion.
  66. if (version_ == schema::Version::Draft2019_09) {
  67. return vocabularies_.contains("/vocab/format");
  68. }
  69. // Draft 2020-12 makes this even more explicit - having separate vocabulary
  70. // documents for "format as assertion" and "format as annotation". Allowing
  71. // validators to add format constraints that are only used for annotating
  72. // results.
  73. return vocabularies_.contains("/vocab/format-assertion");
  74. }
  75. /**
  76. * @brief Is the given "key"word actually a keyword? As in, would
  77. * I expect to resolve a constraint out of it. This is a slightly more
  78. * lenient version of {@see is_constraint} - since it allows keywords that
  79. * have a null factory, as long as they've been registered (e.g. then/else).
  80. *
  81. * @param word The "key"word being looked up (e.g. "if", "properties", ...)
  82. */
  83. bool is_keyword(std::string_view word) const {
  84. return has(word) && metadata_.at(word).type != vocabulary::KeywordType::None;
  85. }
  86. /**
  87. * @brief Does the given "key"word represent a property object - that is to
  88. * say, an object containing some number of schemas mapped by arbitrary keys
  89. *
  90. * @param word The "key"word being looked up (e.g. "if", "properties", ...)
  91. */
  92. bool is_property_keyword(std::string_view word) const {
  93. return has(word) && metadata_.at(word).type == vocabulary::KeywordType::KeywordMap;
  94. }
  95. /**
  96. * @brief Is the given word a real constraint in the Vocabulary. In essence,
  97. * it must be an enabled keyword AND it must have a non-null factory function.
  98. *
  99. * @param word The "key"word being looked up (e.g. "if", "properties", ...)
  100. */
  101. bool is_constraint(std::string_view word) const { return has(word) && metadata_.at(word).make; }
  102. /**
  103. * @brief Fabricate the given constraint if real from the current context
  104. *
  105. * @param word The "key"word being looked up (e.g. "if", "properties", ...)
  106. *
  107. * @param context The current context of schema parsing, used for re-entrancy.
  108. *
  109. * @returns A pair whose first element is either a pointer to a constraint
  110. * (if word represents a supported constraint AND the constraint resolves to
  111. * something meaningful), else null.
  112. *
  113. * The second element is a boolean indicating if the constraint needs to be
  114. * evaluated after other constraints to use their tracking/annotations.
  115. * See the above comments on s_post_constraints for more info.
  116. */
  117. auto constraint(std::string_view word, ParserContext<A> const & context) const {
  118. return std::make_pair(is_constraint(word) ? metadata_.at(word).make(context) : nullptr,
  119. has(word) && metadata_.at(word).is_post_constraint);
  120. }
  121. private:
  122. bool has(std::string_view word) const {
  123. return permitted_.contains(word) && metadata_.contains(word);
  124. }
  125. };
  126. }