vocabulary.h 5.1 KB

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