4 Commit-ok af414740e4 ... e10fb6e947

Szerző SHA1 Üzenet Dátum
  Sam Jaffe e10fb6e947 refactor: clang-tidy for format.h 5 napja
  Sam Jaffe 2032948ce5 Merge branch 'master' into feat/format-matcher 5 napja
  Sam Jaffe b9d4c96bfb refactor: clang-tidy 1 hete
  Sam Jaffe 344bad7fe2 refactor: macro cleanup 1 hete
60 módosított fájl, 821 hozzáadás és 507 törlés
  1. 101 0
      .clang-tidy
  2. 32 2
      include/jvalidate/_macro.h
  3. 16 12
      include/jvalidate/adapter.h
  4. 12 8
      include/jvalidate/adapters/jsoncpp.h
  5. 1 0
      include/jvalidate/compat/compare.h
  6. 9 7
      include/jvalidate/compat/curl.h
  7. 6 5
      include/jvalidate/compat/enumerate.h
  8. 57 38
      include/jvalidate/constraint.h
  9. 7 7
      include/jvalidate/constraint/array_constraint.h
  10. 4 4
      include/jvalidate/constraint/general_constraint.h
  11. 5 5
      include/jvalidate/constraint/number_constraint.h
  12. 6 5
      include/jvalidate/constraint/object_constraint.h
  13. 4 5
      include/jvalidate/constraint/string_constraint.h
  14. 8 6
      include/jvalidate/detail/anchor.h
  15. 3 2
      include/jvalidate/detail/array_iterator.h
  16. 3 2
      include/jvalidate/detail/dynamic_reference_context.h
  17. 4 28
      include/jvalidate/detail/expect.h
  18. 8 4
      include/jvalidate/detail/iostream.h
  19. 6 2
      include/jvalidate/detail/number.h
  20. 5 5
      include/jvalidate/detail/object_iterator.h
  21. 10 3
      include/jvalidate/detail/on_block_exit.h
  22. 10 5
      include/jvalidate/detail/out.h
  23. 6 2
      include/jvalidate/detail/parser_context.h
  24. 27 16
      include/jvalidate/detail/pointer.h
  25. 14 11
      include/jvalidate/detail/reference.h
  26. 1 1
      include/jvalidate/detail/reference_cache.h
  27. 28 21
      include/jvalidate/detail/reference_manager.h
  28. 5 3
      include/jvalidate/detail/relative_pointer.h
  29. 13 0
      include/jvalidate/detail/scoped_state.h
  30. 21 13
      include/jvalidate/detail/simple_adapter.h
  31. 3 3
      include/jvalidate/detail/string.h
  32. 21 15
      include/jvalidate/detail/string_adapter.h
  33. 4 4
      include/jvalidate/detail/tribool.h
  34. 3 1
      include/jvalidate/detail/vocabulary.h
  35. 4 4
      include/jvalidate/document_cache.h
  36. 2 1
      include/jvalidate/enum.h
  37. 84 54
      include/jvalidate/format.h
  38. 39 33
      include/jvalidate/forward.h
  39. 18 12
      include/jvalidate/regex.h
  40. 14 10
      include/jvalidate/schema.h
  41. 8 7
      include/jvalidate/uri.h
  42. 0 2
      include/jvalidate/validation_config.h
  43. 15 22
      include/jvalidate/validation_result.h
  44. 52 40
      include/jvalidate/validation_visitor.h
  45. 14 12
      include/jvalidate/validator.h
  46. 3 1
      include/jvalidate/vocabulary.h
  47. 17 14
      src/validate.cxx
  48. 10 0
      tests/.clang-tidy
  49. 2 2
      tests/adapter_test.cxx
  50. 9 7
      tests/annotation_test.cxx
  51. 6 4
      tests/custom_filter.h
  52. 4 0
      tests/detail_test.cxx
  53. 1 0
      tests/enum_test.cxx
  54. 8 4
      tests/extension_test.cxx
  55. 10 9
      tests/json_schema_test_suite.h
  56. 3 1
      tests/jsoncpp_adapter_test.cxx
  57. 2 2
      tests/matchers.h
  58. 0 1
      tests/regex_test.cxx
  59. 17 12
      tests/selfvalidate_test.cxx
  60. 16 8
      tests/validation_visitor_test.cxx

+ 101 - 0
.clang-tidy

@@ -0,0 +1,101 @@
+---
+Checks: >
+  bugprone-*,
+  -bugprone-std-namespace-modification,
+  -bugprone-exception-escape,
+  -bugprone-macro-parentheses,
+  -bugprone-narrowing-conversions,
+  -bugprone-crtp-constructor-accessibility,
+  clang-diagnostic-*,
+  -clang-diagnostic-pragma-once-outside-header,
+  cppcoreguidelines-*,
+  -cppcoreguidelines-macro-usage,
+  -cppcoreguidelines-c-copy-assignment-signature,
+  -cppcoreguidelines-avoid-const-or-ref-data-members,
+  -cppcoreguidelines-use-enum-class,
+  -cppcoreguidelines-pro-type-member-init,
+  -cppcoreguidelines-pro-type-vararg,
+  -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
+  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+  google-*,
+  -google-readability-namespace-comments,
+  llvm-*,
+  -llvm-header-guard,
+  -llvm-namespace-comment,
+  -llvm-else-after-return,
+  -llvm-prefer-static-over-anonymous-namespace,
+  misc-*,
+  -misc-no-recursion,
+  modernize-*,
+  -modernize-use-trailing-return-type,
+  -modernize-use-nodiscard,
+  performance-*,
+  readability-*,
+  -readability-named-parameter,
+  -readability-redundant-access-specifiers,
+  -readability-use-concise-preprocessor-directives,
+  -readability-magic-numbers,
+
+WarningsAsErrors: ''
+
+HeaderFileExtensions:
+  - ''
+  - h
+  - hh
+  - hpp
+  - hxx
+
+ImplementationFileExtensions:
+  - c
+  - cc
+  - cpp
+  - cxx
+  - tpp
+
+HeaderFilterRegex: '.*'
+ExcludeHeaderFilterRegex: ''
+
+FormatStyle:     none
+User:            samjaffe
+SystemHeaders:   false
+CheckOptions:
+  - key: cppcoreguidelines-avoid-do-while.IgnoreMacros
+    value: true
+  - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
+    value: true
+  - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
+    value: true
+  - key: readability-identifier-naming.NamespaceCase
+    value: lower_case
+  - key: readability-identifier-naming.ClassCase
+    value: CamelCase
+  - key: readability-identifier-naming.StructCase
+    value: CamelCase
+  - key: readability-identifier-naming.PrivateMemberSuffix
+    value: _
+  - key: readability-identifier-naming.FunctionCase
+    value: lower_case
+  - key: readability-identifier-naming.VariableCase
+    value: lower_case
+  - key: readability-identifier-naming.ClassConstexprCase
+    value: lower_case
+  - key: readability-identifier-naming.ClassConstantCase
+    value: lower_case
+  - key: readability-identifier-naming.GlobalConstantCase
+    value: UPPER_CASE
+  - key: readability-identifier-length.IgnoredVariableNames
+    value: "^(_|it|in|ss|[kv])$"
+  - key: readability-identifier-length.IgnoredParameterNames
+    value: "^(os|in|it)$"
+  - key: readability-identifier-length.IgnoredLoopCounterNames
+    value: "^[kvijk_]$"
+  - key: readability-implicit-bool-conversion.AllowPointerConditions
+    value: true
+  - key: readability-implicit-bool-conversion.AllowLogicalOperatorConversion
+    value: true
+  - key: readability-function-cognitive-complexity.IgnoreMacros
+    value: true
+  - key: readability-simplify-boolean-expr.IgnoreMacros
+    value: true
+...
+

+ 32 - 2
include/jvalidate/_macro.h

@@ -1,10 +1,40 @@
 #pragma once
 
-#include <jvalidate/_config.h>
-
 #define JVALIDATE_CONCAT2(A, B) A##B
 #define JVALIDATE_CONCAT(A, B) JVALIDATE_CONCAT2(A, B)
 
 #define JVALIDATE_IIF0(IF, ELSE) ELSE
 #define JVALIDATE_IIF1(IF, ELSE) IF
 #define JVALIDATE_IIF(CONDITIONAL, IF, ELSE) JVALIDATE_CONCAT(JVALIDATE_IIF, CONDITIONAL)(IF, ELSE)
+
+/**
+ * @brief Assert a certain pre/post-condition is true, else return the default
+ * expression (or void).
+ *
+ * @param condition A boolean or boolean-like expression that should be TRUE.
+ * If the condition is FALSE, then the other params are used to produce errors.
+ *
+ * @param ... Zero or One arguments representing the return value if the
+ * condition is FALSE. Zero arguments is equivalent to `return void();`, which
+ * doesn't need to be explicitly stated.
+ */
+#define JVALIDATE_RETURN_UNLESS(condition, ...)                                                    \
+  if (!(condition)) [[unlikely]] {                                                                 \
+    return __VA_ARGS__;                                                                            \
+  }
+
+/**
+ * @brief Assert a certain pre/post-condition is false, else return the default
+ * expression (or void).
+ *
+ * @param condition A boolean or boolean-like expression that should be FALSE.
+ * If the condition is TRUE, then the other params are used to produce errors.
+ *
+ * @param ... Zero or One arguments representing the return value if the
+ * condition is TRUE. Zero arguments is equivalent to `return void();`, which
+ * doesn't need to be explicitly stated.
+ */
+#define JVALIDATE_RETURN_IF(condition, ...)                                                        \
+  if (condition) [[unlikely]] {                                                                    \
+    return __VA_ARGS__;                                                                            \
+  }

+ 16 - 12
include/jvalidate/adapter.h

@@ -1,14 +1,18 @@
 #pragma once
 
 #include <cstdint>
+#include <cstdlib>
 #include <filesystem>
 #include <fstream>
+#include <memory>
 #include <optional>
 #include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
 
-#include <jvalidate/detail/array_iterator.h>
 #include <jvalidate/detail/number.h>
-#include <jvalidate/detail/object_iterator.h>
 #include <jvalidate/enum.h>
 #include <jvalidate/forward.h>
 #include <jvalidate/status.h>
@@ -110,20 +114,20 @@ public:
    * @brief Loop through every element of the JSON array in this node, applying
    * the given callback function to them.
    *
-   * @param cb A callback of the form Adapter => Status
+   * @param callback A callback of the form Adapter => Status
    *
    * @return Status::Accept iff there are no errors
    */
-  virtual Status apply_array(AdapterCallback const & cb) const = 0;
+  virtual Status apply_array(AdapterCallback const & callback) const = 0;
   /**
    * @brief Loop through every element of the JSON object in this node, applying
    * the given callback function to them.
    *
-   * @param cb A callback of the form (string, Adapter) => Status
+   * @param callback A callback of the form (string, Adapter) => Status
    *
    * @return Status::Accept iff there are no errors
    */
-  virtual Status apply_object(ObjectAdapterCallback const & cb) const = 0;
+  virtual Status apply_object(ObjectAdapterCallback const & callback) const = 0;
 
   virtual bool equals(Adapter const & lhs, bool strict) const = 0;
 
@@ -371,11 +375,11 @@ public:
    * @brief Perform an action on this object, such as copying or testing
    * equality.
    *
-   * @param cb A callback function of the form Adapter => Status
+   * @param callback A callback function of the form Adapter => Status
    *
-   * @return the result of cb on the contained JSON
+   * @return the result of callback on the contained JSON
    */
-  virtual Status apply(AdapterCallback const & cb) const = 0;
+  virtual Status apply(AdapterCallback const & callback) const = 0;
   friend std::ostream & operator<<(std::ostream & os, Const const & self) {
     self.apply([&os](Adapter const & adapter) {
       adapter.write(os);
@@ -405,10 +409,10 @@ namespace jvalidate::adapter::detail {
  */
 template <typename JSON> class GenericConst final : public Const {
 public:
-  explicit GenericConst(JSON const & value) : value_(value) {}
+  explicit GenericConst(JSON value) : value_(std::move(value)) {}
 
-  Status apply(AdapterCallback const & cb) const {
-    return cb(typename AdapterTraits<JSON>::ConstAdapter(value_));
+  Status apply(AdapterCallback const & callback) const final {
+    return callback(typename AdapterTraits<JSON>::ConstAdapter(value_));
   }
 
   JSON const & value() const { return value_; }

+ 12 - 8
include/jvalidate/adapters/jsoncpp.h

@@ -1,19 +1,23 @@
 #pragma once
+#include <cstdint>
+#include <istream>
+#include <string>
 #include <type_traits>
 
 #include <json/reader.h>
 #include <json/value.h>
 
 #include <jvalidate/adapter.h>
-#include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/number.h>
 #include <jvalidate/detail/simple_adapter.h>
 #include <jvalidate/enum.h>
+#include <jvalidate/forward.h>
+#include <jvalidate/status.h>
 
 namespace jvalidate::adapter {
 template <>
 inline bool load_stream(std::istream & in, Json::Value & out, std::string & error) noexcept {
-  Json::CharReaderBuilder builder;
+  Json::CharReaderBuilder const builder;
   return Json::parseFromStream(builder, in, &out, &error);
 }
 
@@ -50,7 +54,7 @@ template <typename JSON> class JsonCppAdapter final : public detail::SimpleAdapt
 public:
   using JsonCppAdapter::SimpleAdapter::SimpleAdapter;
 
-  Type type() const {
+  Type type() const final {
     using jvalidate::detail::fits_in_integer;
     switch (const_value().type()) {
     case Json::nullValue:
@@ -73,14 +77,14 @@ public:
     }
   }
 
-  bool as_boolean() const { return const_value().asBool(); }
-  int64_t as_integer() const { return const_value().asInt64(); }
-  double as_number() const { return const_value().asDouble(); }
-  std::string as_string() const { return const_value().asString(); }
+  bool as_boolean() const final { return const_value().asBool(); }
+  int64_t as_integer() const final { return const_value().asInt64(); }
+  double as_number() const final { return const_value().asDouble(); }
+  std::string as_string() const final { return const_value().asString(); }
 
   JsonCppObjectAdapter<JSON> as_object() const { return value(); }
 
-  static std::string key(auto it) { return it.key().asString(); }
+  static std::string key(auto const & it) { return it.key().asString(); }
 
   using detail::SimpleAdapter<JSON>::assign;
   void assign(Adapter const & adapter) const

+ 1 - 0
include/jvalidate/compat/compare.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <compare> // IWYU pragma: keep
+#include <version>
 
 // Apple Clang does not properly support <=> in the STL - so we need to force it
 #if __cpp_lib_three_way_comparison < 201907L

+ 9 - 7
include/jvalidate/compat/curl.h

@@ -1,5 +1,7 @@
 #pragma once
+#include <cstdlib>
 #include <sstream>
+#include <string>
 #include <string_view>
 
 #include <curl/curl.h>
@@ -10,8 +12,8 @@
 
 namespace jvalidate {
 inline size_t transfer_to_buffer(char * data, size_t size, size_t nmemb, void * userdata) {
-  std::stringstream & ss = *reinterpret_cast<std::stringstream *>(userdata);
-  size_t actual_size = size * nmemb;
+  std::stringstream & ss = *static_cast<std::stringstream *>(userdata);
+  size_t const actual_size = size * nmemb;
   ss << std::string_view(data, actual_size);
   return actual_size;
 }
@@ -28,7 +30,7 @@ bool curl_get(jvalidate::URI const & uri, JSON & out, std::string & error) noexc
       curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ss);
       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &transfer_to_buffer);
 
-      CURLcode res = curl_easy_perform(curl);
+      CURLcode const res = curl_easy_perform(curl);
       curl_easy_cleanup(curl);
 
       if (res == CURLE_OK) {
@@ -36,11 +38,11 @@ bool curl_get(jvalidate::URI const & uri, JSON & out, std::string & error) noexc
       }
     }
     return false;
-  } else if (uri.scheme() == "file") {
+  }
+  if (uri.scheme() == "file") {
     return load_file(uri.resource(), out, error);
-  } else {
-    error = "unknown scheme";
-    return false;
   }
+  error = "unknown scheme";
+  return false;
 }
 }

+ 6 - 5
include/jvalidate/compat/enumerate.h

@@ -13,6 +13,7 @@ using std::ranges::views::enumerate;
 }
 #else
 
+#include <cstdlib>
 #include <iterator>
 #include <utility>
 
@@ -27,14 +28,14 @@ namespace jvalidate::detail {
  * attaches the "index" of the iteration to each element - incrementing it as
  * we go.
  */
-template <typename It> class enumurate_iterator {
+template <typename It> class enumurate_iterator { // NOLINT(readability-identifier-naming)
 public:
   using traits_t = std::iterator_traits<It>;
 
   using value_type = std::pair<size_t, typename traits_t::value_type>;
   using reference = std::pair<size_t const &, typename traits_t::reference>;
   using pointer = DerefProxy<reference>;
-  using difference_type = typename traits_t::difference_type;
+  using difference_type = traits_t::difference_type;
   using iterator_category = std::forward_iterator_tag;
 
 private:
@@ -42,7 +43,7 @@ private:
   It iter_;
 
 public:
-  enumurate_iterator(It iter) : iter_(iter) {}
+  explicit(false) enumurate_iterator(It iter) : iter_(std::move(iter)) {}
 
   reference operator*() const { return {index_, *iter_}; }
   pointer operator->() const { return operator*(); }
@@ -53,10 +54,10 @@ public:
     return *this;
   }
 
-  friend bool operator==(enumurate_iterator<It> rhs, enumurate_iterator<It> lhs) {
+  friend bool operator==(enumurate_iterator<It> const & rhs, enumurate_iterator<It> const & lhs) {
     return rhs.iter_ == lhs.iter_;
   }
-  friend bool operator!=(enumurate_iterator<It> rhs, enumurate_iterator<It> lhs) {
+  friend bool operator!=(enumurate_iterator<It> const & rhs, enumurate_iterator<It> const & lhs) {
     return rhs.iter_ != lhs.iter_;
   }
 };

+ 57 - 38
include/jvalidate/constraint.h

@@ -1,20 +1,30 @@
 #pragma once
+// NOLINTBEGIN(readability-identifier-naming)
 
+#include <cstdint>
+#include <cstdlib>
 #include <functional>
+#include <initializer_list>
 #include <map>
 #include <memory>
+#include <optional>
 #include <set>
+#include <stdexcept> // IWYU pragma: keep
+#include <string>
 #include <string_view>
 #include <unordered_map>
 #include <unordered_set>
+#include <utility>
+#include <vector>
 
 #include <jvalidate/constraint/array_constraint.h>
-#include <jvalidate/constraint/extension_constraint.h>
+#include <jvalidate/constraint/extension_constraint.h> // IWYU pragma: keep
 #include <jvalidate/constraint/general_constraint.h>
 #include <jvalidate/constraint/number_constraint.h>
 #include <jvalidate/constraint/object_constraint.h>
 #include <jvalidate/constraint/string_constraint.h>
 
+#include <jvalidate/adapter.h>
 #include <jvalidate/compat/enumerate.h>
 #include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/parser_context.h>
@@ -99,7 +109,7 @@ public:
    */
   struct Versioned {
     template <typename M = vocabulary::Metadata<A>>
-    Versioned(M make) : data{{schema::Version::Earliest, make}} {}
+    explicit(false) Versioned(M make) : data{{schema::Version::Earliest, make}} {}
     Versioned(schema::Version version, vocabulary::Metadata<A> make) : data{{version, make}} {}
     Versioned(std::initializer_list<std::pair<schema::Version const, vocabulary::Metadata<A>>> init)
         : data(init) {}
@@ -175,8 +185,9 @@ private:
 public:
   ConstraintFactory() = default;
 
-  static pConstraint ptr(auto && in) {
-    return std::make_unique<constraint::Constraint>(std::move(in));
+  static pConstraint ptr(auto && cons) {
+    // NOLINTNEXTLINE(bugprone-move-forwarding-reference)
+    return std::make_unique<constraint::Constraint>(std::move(cons));
   }
 
   /**
@@ -427,6 +438,7 @@ public:
    */
   static pConstraint ifThenElse(detail::ParserContext<A> const & context) {
     schema::Node const * then_ = context.fixed_schema(true);
+    EXPECT(context.parent.has_value());
     if (context.parent->contains("then")) {
       then_ = context.neighbor("then").node();
     }
@@ -573,14 +585,15 @@ public:
    * and is not a boolean.
    */
   static auto minimum(detail::ParserContext<A> const & context) {
-    double value = context.schema.as_number();
+    EXPECT(context.parent.has_value());
+    double const value = context.schema.as_number();
     if (context.vocab->version() < schema::Version::Draft06 &&
         context.parent->contains("exclusiveMinimum")) {
       auto exclusive = (*context.parent)["exclusiveMinimum"];
       EXPECT(exclusive.type() == adapter::Type::Boolean);
       return ptr(constraint::MinimumConstraint{value, exclusive.as_boolean()});
     }
-    return ptr(constraint::MinimumConstraint{value, false});
+    return ptr(constraint::MinimumConstraint{.value = value, .exclusive = false});
   }
 
   /**
@@ -595,8 +608,8 @@ public:
    * @throws If the contained value is not interpretable as a number
    */
   static pConstraint exclusiveMinimum(detail::ParserContext<A> const & context) {
-    double value = context.schema.as_number();
-    return ptr(constraint::MinimumConstraint{value, true});
+    double const value = context.schema.as_number();
+    return ptr(constraint::MinimumConstraint{.value = value, .exclusive = true});
   }
 
   /**
@@ -616,14 +629,16 @@ public:
    * and is not a boolean.
    */
   static auto maximum(detail::ParserContext<A> const & context) {
-    double value = context.schema.as_number();
+    EXPECT(context.parent.has_value());
+    double const value = context.schema.as_number();
     if (context.vocab->version() < schema::Version::Draft06 &&
         context.parent->contains("exclusiveMaximum")) {
       auto exclusive = (*context.parent)["exclusiveMaximum"];
       EXPECT(exclusive.type() == adapter::Type::Boolean);
-      return ptr(constraint::MaximumConstraint{value, exclusive.as_boolean()});
+      return ptr(
+          constraint::MaximumConstraint{.value = value, .exclusive = exclusive.as_boolean()});
     }
-    return ptr(constraint::MaximumConstraint{value, false});
+    return ptr(constraint::MaximumConstraint{.value = value, .exclusive = false});
   }
 
   /**
@@ -638,8 +653,8 @@ public:
    * @throws If the contained value is not interpretable as a number
    */
   static pConstraint exclusiveMaximum(detail::ParserContext<A> const & context) {
-    double value = context.schema.as_number();
-    return ptr(constraint::MaximumConstraint{value, true});
+    double const value = context.schema.as_number();
+    return ptr(constraint::MaximumConstraint{.value = value, .exclusive = true});
   }
 
   /**
@@ -660,7 +675,7 @@ public:
    * @throws If the contained value is not interpretable as a number
    */
   static auto multipleOf(detail::ParserContext<A> const & context) {
-    double value = context.schema.as_number();
+    double const value = context.schema.as_number();
     return ptr(constraint::MultipleOfConstraint{value});
   }
 
@@ -681,9 +696,9 @@ public:
    * @throws If the contained value is not interpretable as an integer
    */
   static auto minLength(detail::ParserContext<A> const & context) {
-    EXPECT(context.schema.type() == adapter::Type::Integer ||
-           context.schema.type() == adapter::Type::Number);
-    return ptr(constraint::MinLengthConstraint{context.schema.as_integer()});
+    std::optional<int64_t> value = context.schema.maybe_integer(true);
+    EXPECT_M(value.has_value() && *value >= 0, "Must be an integer >= 0");
+    return ptr(constraint::MinLengthConstraint{static_cast<size_t>(*value)});
   }
 
   /**
@@ -701,9 +716,9 @@ public:
    * @throws If the contained value is not interpretable as an integer
    */
   static auto maxLength(detail::ParserContext<A> const & context) {
-    EXPECT(context.schema.type() == adapter::Type::Integer ||
-           context.schema.type() == adapter::Type::Number);
-    return ptr(constraint::MaxLengthConstraint{context.schema.as_integer()});
+    std::optional<int64_t> value = context.schema.maybe_integer(true);
+    EXPECT_M(value.has_value() && *value >= 0, "Must be an integer >= 0");
+    return ptr(constraint::MaxLengthConstraint{static_cast<size_t>(*value)});
   }
 
   /**
@@ -773,6 +788,7 @@ public:
    * @returns A ContainsConstraint, with optional minimum and maximum matching
    */
   static auto contains(detail::ParserContext<A> const & context) {
+    EXPECT(context.parent.has_value());
     if (context.vocab->version() < schema::Version::Draft2019_09) {
       return ptr(constraint::ContainsConstraint{context.node()});
     }
@@ -803,9 +819,9 @@ public:
    * @throws If the contained value is not interpretable as an integer
    */
   static auto minItems(detail::ParserContext<A> const & context) {
-    EXPECT(context.schema.type() == adapter::Type::Integer ||
-           context.schema.type() == adapter::Type::Number);
-    return ptr(constraint::MinItemsConstraint{context.schema.as_integer()});
+    std::optional<int64_t> value = context.schema.maybe_integer(true);
+    EXPECT_M(value.has_value() && *value >= 0, "Must be an integer >= 0");
+    return ptr(constraint::MinItemsConstraint{static_cast<size_t>(*value)});
   }
 
   /**
@@ -822,9 +838,9 @@ public:
    * @throws If the contained value is not interpretable as an integer
    */
   static auto maxItems(detail::ParserContext<A> const & context) {
-    EXPECT(context.schema.type() == adapter::Type::Integer ||
-           context.schema.type() == adapter::Type::Number);
-    return ptr(constraint::MaxItemsConstraint{context.schema.as_integer()});
+    std::optional<int64_t> value = context.schema.maybe_integer(true);
+    EXPECT_M(value.has_value() && *value >= 0, "Must be an integer >= 0");
+    return ptr(constraint::MaxItemsConstraint{static_cast<size_t>(*value)});
   }
 
   /**
@@ -872,6 +888,7 @@ public:
    * @throws if the precondition is broken
    */
   static pConstraint additionalItems(detail::ParserContext<A> const & context) {
+    EXPECT(context.parent.has_value());
     std::string const prefix =
         context.vocab->version() >= schema::Version::Draft2020_12 ? "prefixItems" : "items";
 
@@ -885,16 +902,16 @@ public:
       return nullptr;
     }
 
-    size_t const n = parent[prefix].array_size();
+    size_t const elems = parent[prefix].array_size();
     // Prior to Draft06, boolean schemas were not allowed in a general context,
     // they were instead reserved for the "additionalItems" and
     // "additionalProperties" keywords.
     if (context.vocab->version() < schema::Version::Draft06 &&
         context.schema.type() == adapter::Type::Boolean) {
-      return ptr(constraint::AdditionalItemsConstraint{context.always(), n});
+      return ptr(constraint::AdditionalItemsConstraint{context.always(), elems});
     }
 
-    return ptr(constraint::AdditionalItemsConstraint{context.node(), n});
+    return ptr(constraint::AdditionalItemsConstraint{context.node(), elems});
   }
 
   /**
@@ -1006,9 +1023,9 @@ public:
    * @throws If the contained value is not interpretable as an integer
    */
   static auto minProperties(detail::ParserContext<A> const & context) {
-    EXPECT(context.schema.type() == adapter::Type::Integer ||
-           context.schema.type() == adapter::Type::Number);
-    return ptr(constraint::MinPropertiesConstraint{context.schema.as_integer()});
+    std::optional<int64_t> value = context.schema.maybe_integer(true);
+    EXPECT_M(value.has_value() && *value >= 0, "Must be an integer >= 0");
+    return ptr(constraint::MinPropertiesConstraint{static_cast<size_t>(*value)});
   }
 
   /**
@@ -1025,9 +1042,9 @@ public:
    * @throws If the contained value is not interpretable as an integer
    */
   static auto maxProperties(detail::ParserContext<A> const & context) {
-    EXPECT(context.schema.type() == adapter::Type::Integer ||
-           context.schema.type() == adapter::Type::Number);
-    return ptr(constraint::MaxPropertiesConstraint{context.schema.as_integer()});
+    std::optional<int64_t> value = context.schema.maybe_integer(true);
+    EXPECT_M(value.has_value() && *value >= 0, "Must be an integer >= 0");
+    return ptr(constraint::MaxPropertiesConstraint{static_cast<size_t>(*value)});
   }
 
   /**
@@ -1178,6 +1195,7 @@ public:
    * @returns An AdditionalPropertiesConstraint
    */
   static auto additionalProperties(detail::ParserContext<A> const & context) {
+    EXPECT(context.parent.has_value());
     std::unordered_set<std::string> properties;
     std::vector<std::string> patterns;
 
@@ -1252,7 +1270,7 @@ public:
       }
     }
 
-    return ptr(constraint::DependenciesConstraint{schemas, required});
+    return ptr(constraint::DependenciesConstraint{.subschemas = schemas, .required = required});
   }
 
   /**
@@ -1283,7 +1301,7 @@ public:
       rval.emplace(prop, context.child(subschema, prop).node());
     }
 
-    return ptr(constraint::DependenciesConstraint{rval});
+    return ptr(constraint::DependenciesConstraint{.subschemas = rval});
   }
 
   /**
@@ -1317,7 +1335,8 @@ public:
       }
     }
 
-    return ptr(constraint::DependenciesConstraint{{}, rval});
+    return ptr(constraint::DependenciesConstraint{.subschemas = {}, .required = rval});
   }
 };
 }
+// NOLINTEND(readability-identifier-naming)

+ 7 - 7
include/jvalidate/constraint/array_constraint.h

@@ -1,9 +1,9 @@
 #pragma once
 
+#include <cstdlib>
 #include <optional>
 #include <vector>
 
-#include <jvalidate/adapter.h>
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
@@ -16,8 +16,8 @@ namespace jvalidate::constraint {
  * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.1.2
  */
 struct AdditionalItemsConstraint {
-  schema::Node const * subschema;
-  size_t applies_after_nth;
+  schema::Node const * subschema = nullptr;
+  size_t applies_after_nth = 0;
 };
 
 /**
@@ -33,7 +33,7 @@ struct AdditionalItemsConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4.5
  */
 struct ContainsConstraint {
-  schema::Node const * subschema;
+  schema::Node const * subschema = nullptr;
   std::optional<size_t> minimum = std::nullopt;
   std::optional<size_t> maximum = std::nullopt;
 };
@@ -47,7 +47,7 @@ struct ContainsConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4.1
  */
 struct MaxItemsConstraint {
-  int64_t value;
+  size_t value = 0;
 };
 
 /**
@@ -59,7 +59,7 @@ struct MaxItemsConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4.2
  */
 struct MinItemsConstraint {
-  int64_t value;
+  size_t value = 0;
 };
 
 /**
@@ -81,7 +81,7 @@ struct TupleConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-core#section-11.2
  */
 struct UnevaluatedItemsConstraint {
-  schema::Node const * subschema;
+  schema::Node const * subschema = nullptr;
 };
 
 /**

+ 4 - 4
include/jvalidate/constraint/general_constraint.h

@@ -1,11 +1,11 @@
 #pragma once
 
+#include <cstdlib>
 #include <memory>
 #include <set>
 #include <vector>
 
 #include <jvalidate/forward.h>
-#include <jvalidate/status.h>
 
 namespace jvalidate::constraint {
 /**
@@ -86,9 +86,9 @@ struct OneOfConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.2
  */
 struct ConditionalConstraint {
-  schema::Node const * if_constraint;
-  schema::Node const * then_constraint;
-  schema::Node const * else_constraint;
+  schema::Node const * if_constraint = nullptr;
+  schema::Node const * then_constraint = nullptr;
+  schema::Node const * else_constraint = nullptr;
 };
 
 /**

+ 5 - 5
include/jvalidate/constraint/number_constraint.h

@@ -18,8 +18,8 @@ namespace jvalidate::constraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2.3
  */
 struct MaximumConstraint {
-  double value;
-  bool exclusive;
+  double value = 0;
+  bool exclusive = false;
 
   bool operator()(double arg) const { return exclusive ? arg < value : arg <= value; }
 };
@@ -35,8 +35,8 @@ struct MaximumConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2.5
  */
 struct MinimumConstraint {
-  double value;
-  bool exclusive;
+  double value = 0;
+  bool exclusive = false;
 
   bool operator()(double arg) const { return exclusive ? arg > value : arg >= value; }
 };
@@ -50,7 +50,7 @@ struct MinimumConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2.1
  */
 struct MultipleOfConstraint {
-  double value;
+  double value = 1;
 
   bool operator()(double arg) const {
     if (std::fabs(std::remainder(arg, value)) <= std::numeric_limits<double>::epsilon()) {

+ 6 - 5
include/jvalidate/constraint/object_constraint.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cstdlib>
 #include <map>
 #include <string>
 #include <unordered_set>
@@ -18,7 +19,7 @@ namespace jvalidate::constraint {
  * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2.3
  */
 struct AdditionalPropertiesConstraint {
-  schema::Node const * subschema;
+  schema::Node const * subschema = nullptr;
   std::unordered_set<std::string> properties;
   std::vector<std::string> patterns;
 };
@@ -50,7 +51,7 @@ struct DependenciesConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.1
  */
 struct MaxPropertiesConstraint {
-  int64_t value;
+  size_t value = 0;
 };
 
 /**
@@ -62,7 +63,7 @@ struct MaxPropertiesConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.2
  */
 struct MinPropertiesConstraint {
-  int64_t value;
+  size_t value = 0;
 };
 
 /**
@@ -101,7 +102,7 @@ struct PropertiesConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2.4
  */
 struct PropertyNamesConstraint {
-  schema::Node const * key_schema;
+  schema::Node const * key_schema = nullptr;
 };
 
 /**
@@ -125,6 +126,6 @@ struct RequiredConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-core#section-11.3
  */
 struct UnevaluatedPropertiesConstraint {
-  schema::Node const * subschema;
+  schema::Node const * subschema = nullptr;
 };
 }

+ 4 - 5
include/jvalidate/constraint/string_constraint.h

@@ -1,10 +1,9 @@
 #pragma once
 
+#include <cstdlib>
 #include <string>
 
-#include <jvalidate/detail/string.h>
 #include <jvalidate/enum.h>
-#include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
 /**
@@ -16,7 +15,7 @@ namespace jvalidate::constraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.3.2
  */
 struct MinLengthConstraint {
-  int64_t value;
+  size_t value = 0;
 };
 
 /**
@@ -28,7 +27,7 @@ struct MinLengthConstraint {
  * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.3.1
  */
 struct MaxLengthConstraint {
-  int64_t value;
+  size_t value = 0;
 };
 
 /**
@@ -55,6 +54,6 @@ struct PatternConstraint {
 struct FormatConstraint {
   std::string format;
   schema::Version for_version;
-  bool is_assertion;
+  bool is_assertion = false;
 };
 }

+ 8 - 6
include/jvalidate/detail/anchor.h

@@ -2,11 +2,11 @@
 
 #include <algorithm>
 #include <cctype>
-#include <compare>
+#include <ostream>
 #include <string>
 #include <string_view>
 
-#include <jvalidate/compat/compare.h>
+#include <jvalidate/compat/compare.h> // IWYU pragma: keep
 #include <jvalidate/detail/expect.h>
 
 namespace jvalidate::detail {
@@ -45,10 +45,8 @@ public:
   explicit Anchor(std::string_view content) : content_(content) {
     EXPECT_M(content.empty() || content[0] == '_' || std::isalpha(content[0]),
              "First character of an Anchor must be alphabetic or '_'");
-    EXPECT_M(
-        std::all_of(content.begin(), content.end(),
-                    [](char c) { return std::isalnum(c) || c == '_' || c == '.' || c == '-'; }),
-        "Illegal character(s) in anchor");
+    EXPECT_M(std::ranges::all_of(content, &Anchor::is_valid_char),
+             "Illegal character(s) in anchor");
   }
 
   explicit operator std::string const &() const { return content_; }
@@ -59,5 +57,9 @@ public:
   }
 
   auto operator<=>(Anchor const & lhs) const = default;
+
+private:
+  // NOLINTNEXTLINE(readability-identifier-length,readability-implicit-bool-conversion)
+  static bool is_valid_char(char c) { return std::isalnum(c) || c == '_' || c == '.' || c == '-'; }
 };
 }

+ 3 - 2
include/jvalidate/detail/array_iterator.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cstddef>
 #include <iterator>
 
 #include <jvalidate/detail/deref_proxy.h>
@@ -21,11 +22,11 @@ public:
   using value_type = Adapter;
   using reference = Adapter;
   using pointer = ::jvalidate::detail::DerefProxy<reference>;
-  using difference_type = std::ptrdiff_t;
+  using difference_type = ptrdiff_t;
   using iterator_category = std::forward_iterator_tag;
 
   JsonArrayIterator() = default; // Sentinel for handling null objects
-  JsonArrayIterator(It it) : It(it) {}
+  explicit(false) JsonArrayIterator(It const & it) : It(it) {}
 
   reference operator*() const { return Adapter(It::operator*()); }
 

+ 3 - 2
include/jvalidate/detail/dynamic_reference_context.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cstdlib>
 #include <deque>
 #include <map>
 #include <optional>
@@ -56,10 +57,10 @@ public:
     // document (i.e. that doc does not define a specific $dynamicAnchor).
     for (auto & [k, stack] : data_) {
       if (not frame.contains(k)) {
-        stack.push_back(std::nullopt);
+        stack.emplace_back(std::nullopt);
       }
       while (stack.size() < sources_.size()) {
-        stack.push_front(std::nullopt);
+        stack.emplace_front(std::nullopt);
       }
     }
 

+ 4 - 28
include/jvalidate/detail/expect.h

@@ -1,15 +1,7 @@
 #pragma once
 
-#include <iostream>
-#include <sstream>
-
-#if defined(__clang__) || defined(__GNUC__)
-#define JVALIDATE_LIKELY(x) __builtin_expect(!!(x), 1)
-#define JVALIDATE_UNLIKELY(x) __builtin_expect(!!(x), 0)
-#else
-#define JVALIDATE_LIKELY(x) (x)
-#define JVALIDATE_UNLIKELY(x) (x)
-#endif
+#include <iostream> // IWYU pragma: keep
+#include <sstream>  // IWYU pragma: keep
 
 #if defined(JVALIDATE_USE_EXCEPTIONS)
 /**
@@ -38,7 +30,7 @@
  */
 #define JVALIDATE_THROW(extype, message)                                                           \
   do {                                                                                             \
-    std::cerr << message << std::endl;                                                             \
+    std::cerr << message << "\n";                                                                  \
     std::terminate();                                                                              \
   } while (false)
 #endif
@@ -58,7 +50,7 @@
  * output chain (e.g. `"unsupported index " << i << ", valid items " << items`).
  */
 #define EXPECT_T(condition, extype, message)                                                       \
-  if (JVALIDATE_UNLIKELY(!(condition))) {                                                          \
+  if (!(condition)) [[unlikely]] { /* NOLINT(readability-simplify-boolean-expr) */                 \
     JVALIDATE_THROW(extype, message);                                                              \
   }
 
@@ -81,19 +73,3 @@
  * If the condition is FALSE, then the other params are used to produce errors.
  */
 #define EXPECT(condition) EXPECT_M(condition, #condition " at " __FILE__ ":" << __LINE__)
-
-/**
- * @brief Assert a certain pre/post-condition is true, else return the default
- * expression (or void).
- *
- * @param condition A boolean or boolean-like expression that should be TRUE.
- * If the condition is FALSE, then the other params are used to produce errors.
- *
- * @param ... Zero or One arguments representing the return value if the
- * condition is FALSE. Zero arguments is equivalent to `return void();`, which
- * doesn't need to be explicitly stated.
- */
-#define RETURN_UNLESS(condition, ...)                                                              \
-  if (JVALIDATE_UNLIKELY(!(condition))) {                                                          \
-    return __VA_ARGS__;                                                                            \
-  }

+ 8 - 4
include/jvalidate/detail/iostream.h

@@ -3,7 +3,10 @@
 #include <iostream>
 #include <set>
 #include <sstream>
+#include <string>
 #include <unordered_set>
+#include <utility>
+#include <vector>
 
 #include <jvalidate/enum.h>
 #include <jvalidate/status.h>
@@ -49,11 +52,11 @@ inline std::ostream & operator<<(std::ostream & os, Version version) {
 }
 
 namespace jvalidate {
-inline std::ostream & operator<<(std::ostream & os, Status st) {
-  if (st == Status::Accept) {
+inline std::ostream & operator<<(std::ostream & os, Status stat) {
+  if (stat == Status::Accept) {
     return os << "Accept";
   }
-  if (st == Status::Reject) {
+  if (stat == Status::Reject) {
     return os << "Reject";
   }
   return os << "Noop";
@@ -97,7 +100,7 @@ static std::string to_string(S const & str) {
 static std::string to_string(auto const &... args) {
   std::stringstream ss;
   using ::jvalidate::operator<<;
-  [[maybe_unused]] int _[] = {(ss << args, 0)...};
+  (ss << ... << args);
   return ss.str();
 }
 
@@ -105,6 +108,7 @@ static std::string to_string(auto const &... args) {
 // an error.
 static std::vector<std::string> to_string_list(auto const & arg) {
   std::vector<std::string> strs;
+  strs.reserve(arg.size());
   for (auto const & elem : arg) {
     strs.push_back(::jvalidate::to_string(elem));
   }

+ 6 - 2
include/jvalidate/detail/number.h

@@ -11,8 +11,10 @@
 #include <charconv>
 #include <cmath>
 #include <concepts>
+#include <cstdint>
 #include <limits>
 #include <stdexcept>
+#include <string>
 #include <string_view>
 #include <system_error>
 
@@ -28,16 +30,18 @@ inline bool is_json_integer(double number) { return std::floor(number) == number
  * actually fits in the 64-bit integer type that we use for JSON Integer.
  */
 inline bool fits_in_integer(double number) {
-  static constexpr double g_int_max = static_cast<double>(std::numeric_limits<int64_t>::max());
-  static constexpr double g_int_min = static_cast<double>(std::numeric_limits<int64_t>::min());
+  static constexpr auto g_int_max = static_cast<double>(std::numeric_limits<int64_t>::max());
+  static constexpr auto g_int_min = static_cast<double>(std::numeric_limits<int64_t>::min());
   return is_json_integer(number) && number <= g_int_max && number >= g_int_min;
 }
 
 /**
  * @brief Determine if an unsigned integer fits into a signed integer
  */
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
 inline bool fits_in_integer(uint64_t number) { return (number & 0x8000'0000'0000'0000) == 0; }
 
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
 template <std::integral I> I from_str(std::string_view str, int base = 10) {
   I rval;
   auto [end, ec] = std::from_chars(str.begin(), str.end(), rval, base);

+ 5 - 5
include/jvalidate/detail/object_iterator.h

@@ -1,7 +1,9 @@
 #pragma once
 
+#include <cstddef>
 #include <iterator>
 #include <string>
+#include <utility>
 
 #include <jvalidate/detail/deref_proxy.h>
 
@@ -32,15 +34,13 @@ public:
   // (such as if they do not store a default empty key).
   using reference = std::pair<std::string, Adapter>;
   using pointer = ::jvalidate::detail::DerefProxy<reference>;
-  using difference_type = std::ptrdiff_t;
+  using difference_type = ptrdiff_t;
   using iterator_category = std::forward_iterator_tag;
 
   JsonObjectIterator() = default; // Sentinel for handling null objects
-  JsonObjectIterator(It it) : It(it) {}
+  explicit(false) JsonObjectIterator(It const & it) : It(it) {}
 
-  reference operator*() const {
-    return { Adapter::key(*this), Adapter(It::operator->()) };
-  }
+  reference operator*() const { return {Adapter::key(*this), Adapter(It::operator->())}; }
 
   pointer operator->() const { return {operator*()}; }
 

+ 10 - 3
include/jvalidate/detail/on_block_exit.h

@@ -1,6 +1,8 @@
 #pragma once
 
+#include <concepts>
 #include <functional>
+#include <utility>
 
 namespace jvalidate::detail {
 /**
@@ -17,13 +19,18 @@ private:
 
 public:
   OnBlockExit() = default;
-  template <typename F> OnBlockExit(F && callback) : callback_(callback) {}
+  template <typename F>
+    requires std::constructible_from<std::function<void()>, F>
+  explicit(false) OnBlockExit(F && callback) : callback_(std::forward<F>(callback)) {}
+
+  OnBlockExit(OnBlockExit const &) = delete;
+  OnBlockExit & operator=(OnBlockExit const &) = delete;
 
   // Must be explicity implemented because function's move ctor is non-destructive
-  OnBlockExit(OnBlockExit && other) { std::swap(other.callback_, callback_); }
+  OnBlockExit(OnBlockExit && other) noexcept { std::swap(other.callback_, callback_); }
 
   // Must be explicity implemented because function's move-assign is non-destructive
-  OnBlockExit & operator=(OnBlockExit && other) {
+  OnBlockExit & operator=(OnBlockExit && other) noexcept {
     std::swap(other.callback_, callback_);
     return *this;
   }

+ 10 - 5
include/jvalidate/detail/out.h

@@ -1,4 +1,6 @@
 #pragma once
+// NOLINTBEGIN(google-explicit-constructor, readability-identifier-naming,
+// misc-unconventional-assign-operator)
 
 #include <type_traits>
 #include <utility>
@@ -99,8 +101,9 @@ public:
    */
   operator T const &() const {
     struct {
-      T const & operator()(T const & in) const { return in; }
-      T const & operator()(T * in) const { return *in; }
+      T const & operator()(T &&) const = delete; // Illustrative
+      T const & operator()(T const & arg) const { return arg; }
+      T const & operator()(T * arg) const { return *arg; }
     } visitor;
     return std::visit(visitor, ref_);
   }
@@ -120,11 +123,13 @@ public:
     requires std::is_constructible_v<T, U>
   T const & operator=(U && val) {
     struct {
-      U && val;
-      T const & operator()(T & in) const { return in = std::forward<U>(val); }
-      T const & operator()(T * in) const { return *in = std::forward<U>(val); }
+      U && val; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
+      T const & operator()(T & arg) const { return arg = std::forward<U>(val); }
+      T const & operator()(T * arg) const { return *arg = std::forward<U>(val); }
     } visitor{std::forward<U>(val)};
     return std::visit(visitor, ref_);
   }
 };
 }
+// NOLINTEND(google-explicit-constructor, readability-identifier-naming,
+// misc-unconventional-assign-operator)

+ 6 - 2
include/jvalidate/detail/parser_context.h

@@ -1,7 +1,10 @@
 #pragma once
 
+#include <cstdlib>
 #include <optional>
+#include <string>
 
+#include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/reference.h>
 #include <jvalidate/detail/vocabulary.h>
 #include <jvalidate/forward.h>
@@ -18,8 +21,8 @@ template <Adapter A> struct ParserContext {
   ReferenceManager<A> & ref;
 
   std::optional<Object> parent = std::nullopt;
-  Reference where = {};
-  Reference dynamic_where = {};
+  Reference where;
+  Reference dynamic_where;
 
   /**
    * @brief Obtain the ParserContext for an arbitrary schema location,
@@ -68,6 +71,7 @@ template <Adapter A> struct ParserContext {
    * @param key The object-key to the new schema
    */
   ParserContext neighbor(std::string const & key) const {
+    EXPECT(parent.has_value());
     return rebind((*parent)[key], where.parent() / key, dynamic_where.parent() / key, parent);
   }
 

+ 27 - 16
include/jvalidate/detail/pointer.h

@@ -2,13 +2,17 @@
 
 #include <algorithm>
 #include <cassert>
+#include <cstddef>
+#include <cstdlib>
 #include <iostream>
+#include <stdexcept> // IWYU pragma: keep
 #include <string>
 #include <string_view>
+#include <utility>
 #include <variant>
 #include <vector>
 
-#include <jvalidate/compat/compare.h>
+#include <jvalidate/compat/compare.h> // IWYU pragma: keep
 #include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/number.h>
 #include <jvalidate/forward.h>
@@ -28,13 +32,14 @@ namespace jvalidate::detail {
  * where we use parent to rewind the path back to the owning scope for
  * if-then-else processing.
  */
-struct parent_t {};
-constexpr parent_t parent;
+struct parent_t {};        // NOLINT(readability-identifier-naming)
+constexpr parent_t parent; // NOLINT(readability-identifier-naming)
 
 class Pointer {
 public:
   Pointer() = default;
-  Pointer(std::vector<std::variant<std::string, size_t>> const & tokens) : tokens_(tokens) {}
+  explicit(false) Pointer(std::vector<std::variant<std::string, size_t>> const & tokens)
+      : tokens_(tokens) {}
 
   /**
    * @brief Parse a JSON-Pointer from a serialized JSON-Pointer-String. In
@@ -43,7 +48,7 @@ public:
    * valid - and therefore that an invalidly formatter pointer string will
    * point to somewhere non-existant (since it will be used in schema handling)
    */
-  Pointer(std::string_view path) {
+  explicit(false) Pointer(std::string_view path) {
     if (path.empty()) {
       return;
     }
@@ -55,7 +60,8 @@ public:
       // assume that we are not going to use those kinds of paths in a reference
       // field. Therefore we don't need to include any clever tricks for storage
       if (not in.empty() && in.find_first_not_of("0123456789") == std::string::npos) {
-        return tokens_.push_back(from_str<size_t>(in));
+        tokens_.emplace_back(from_str<size_t>(in));
+        return;
       }
 
       for (size_t i = 0; i < in.size(); ++i) {
@@ -64,10 +70,12 @@ public:
         // than '/' and '~' to be handled in all contexts.
         // TODO(samjaffe): Only do this if enc is hex-like (currently throws?)
         if (in[i] == '%') {
-          std::string_view enc = std::string_view(in).substr(i + 1, 2);
+          std::string_view const enc = std::string_view(in).substr(i + 1, 2);
+          // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
           in.replace(i, 3, 1, from_str<char>(enc, 16));
           continue;
-        } else if (in[i] != '~') {
+        }
+        if (in[i] != '~') {
           // Not a special char-sequence, does not need massaging
           continue;
         }
@@ -83,7 +91,7 @@ public:
           JVALIDATE_THROW(std::runtime_error, "Illegal ~ code");
         }
       }
-      tokens_.push_back(std::move(in));
+      tokens_.emplace_back(std::move(in));
     };
 
     // JSON-Pointers are required to start with a '/'.
@@ -93,9 +101,9 @@ public:
     // strict character: then that character would be escaped, using the above
     // rules. We take advantage of string_view's sliding view to make iteration
     // easy.
-    for (size_t p = path.find('/'); p != std::string::npos;
-         path.remove_prefix(p + 1), p = path.find('/')) {
-      append_with_parse(std::string(path.substr(0, p)));
+    for (size_t pos = path.find('/'); pos != std::string::npos;
+         path.remove_prefix(pos + 1), pos = path.find('/')) {
+      append_with_parse(std::string(path.substr(0, pos)));
     }
 
     append_with_parse(std::string(path));
@@ -180,10 +188,13 @@ public:
    */
   Pointer relative_to(Pointer const & other) const {
     assert(starts_with(other));
-    return Pointer(std::vector(tokens_.begin() + other.tokens_.size(), tokens_.end()));
+    return {
+        std::vector(tokens_.begin() + static_cast<ptrdiff_t>(other.tokens_.size()), tokens_.end())};
   }
 
-  Pointer parent(size_t i = 1) const { return Pointer({tokens_.begin(), tokens_.end() - i}); }
+  Pointer parent(size_t levels = 1) const {
+    return {{tokens_.begin(), tokens_.end() - static_cast<ptrdiff_t>(levels)}};
+  }
 
   Pointer & operator/=(Pointer const & relative) {
     tokens_.insert(tokens_.end(), relative.tokens_.begin(), relative.tokens_.end());
@@ -215,13 +226,13 @@ public:
 
   friend std::ostream & operator<<(std::ostream & os, Pointer const & self) {
     for (auto const & elem : self.tokens_) {
-      std::visit([&os](auto const & v) { os << '/' << v; }, elem);
+      std::visit([&os](auto const & tok) { os << '/' << tok; }, elem);
     }
     return os;
   }
   auto operator<=>(Pointer const &) const = default;
 
 private:
-  std::vector<std::variant<std::string, size_t>> tokens_{};
+  std::vector<std::variant<std::string, size_t>> tokens_;
 };
 }

+ 14 - 11
include/jvalidate/detail/reference.h

@@ -1,7 +1,10 @@
 #pragma once
 
+#include <cstdlib>
+#include <ostream>
 #include <string>
 #include <string_view>
+#include <utility>
 
 #include <jvalidate/detail/anchor.h>
 #include <jvalidate/detail/expect.h>
@@ -35,8 +38,8 @@ public:
   RootReference() = default;
 
   // Piecewise constructor for RootReference, supports (URI) and (URI, Anchor)
-  explicit RootReference(URI const & uri, Anchor const & anchor = {})
-      : uri_(uri), anchor_(anchor) {}
+  explicit RootReference(URI uri, Anchor anchor = {})
+      : uri_(std::move(uri)), anchor_(std::move(anchor)) {}
 
   // Parser-ctor for RootReference, implemented in terms of
   // {@see RootReference::RootReference(std::string_view, out<size_t>)}, which
@@ -70,7 +73,7 @@ private:
 
     // As also mentioned in URI, the fragment identifier is used in a
     // JSON-Reference to separate the URI from the Anchor/Pointer component(s)
-    size_t end_of_uri = ref.find('#');
+    size_t const end_of_uri = ref.find('#');
     uri_ = URI(ref.substr(0, end_of_uri));
     // If there is not a fragment-separator, then this RootReference is all URI
     // There will be no Anchor or JSON-Pointer components to extract.
@@ -111,16 +114,16 @@ public:
   Reference() = default;
 
   // Piecewise constructor for References (URI) and (URI, Pointer)
-  explicit Reference(URI const & uri, Pointer const & pointer = {})
-      : root_(uri), pointer_(pointer) {}
+  explicit Reference(URI const & uri, Pointer pointer = {})
+      : root_(uri), pointer_(std::move(pointer)) {}
 
   // Piecewise constructor for References (URI, Anchor) and (URI, Anchor, Pointer)
-  explicit Reference(URI const & uri, Anchor const & anchor, Pointer const & pointer = {})
-      : root_(uri, anchor), pointer_(pointer) {}
+  explicit Reference(URI const & uri, Anchor const & anchor, Pointer pointer = {})
+      : root_(uri, anchor), pointer_(std::move(pointer)) {}
 
   // Piecewise constructor for References using RootReference
-  explicit Reference(RootReference const & root, Pointer const & pointer = {})
-      : root_(root), pointer_(pointer) {}
+  explicit Reference(RootReference root, Pointer pointer = {})
+      : root_(std::move(root)), pointer_(std::move(pointer)) {}
 
   explicit Reference(std::string_view ref) {
     size_t pointer_start = 0;
@@ -141,13 +144,13 @@ public:
    * @brief Delegate function for {@see Pointer::operator/=}, but returning
    * this Reference.
    */
-  Reference & operator/=(auto const & in) { return (pointer_ /= in, *this); }
+  Reference & operator/=(auto const & next) { return (pointer_ /= next, *this); }
 
   /**
    * @brief Delegate function for {@see Pointer::operator/}, but returning
    * a Reference.
    */
-  Reference operator/(auto const & in) const { return Reference(*this) /= in; }
+  Reference operator/(auto const & next) const { return Reference(*this) /= next; }
 
   friend std::ostream & operator<<(std::ostream & os, Reference const & self) {
     return os << self.root_ << self.pointer_;

+ 1 - 1
include/jvalidate/detail/reference_cache.h

@@ -2,10 +2,10 @@
 
 #include <functional>
 #include <map>
-#include <optional>
 
 #include <jvalidate/detail/pointer.h>
 #include <jvalidate/detail/reference.h>
+#include <jvalidate/uri.h>
 
 namespace jvalidate::detail {
 /**

+ 28 - 21
include/jvalidate/detail/reference_manager.h

@@ -1,7 +1,13 @@
 #pragma once
 
+#include <cstdlib>
 #include <map>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
 #include <unordered_set>
+#include <utility>
 
 #include <jvalidate/compat/enumerate.h>
 #include <jvalidate/detail/anchor.h>
@@ -41,15 +47,6 @@ namespace jvalidate::detail {
  */
 template <Adapter A> class ReferenceManager {
 private:
-  static inline std::map<std::string_view, schema::Version> const g_schema_ids{
-      {"json-schema.org/draft-03/schema", schema::Version::Draft03},
-      {"json-schema.org/draft-04/schema", schema::Version::Draft04},
-      {"json-schema.org/draft-06/schema", schema::Version::Draft06},
-      {"json-schema.org/draft-07/schema", schema::Version::Draft07},
-      {"json-schema.org/draft/2019-09/schema", schema::Version::Draft2019_09},
-      {"json-schema.org/draft/2020-12/schema", schema::Version::Draft2020_12},
-  };
-
   ConstraintFactory<A> const & constraints_;
   DocumentCache<A> & external_;
 
@@ -113,8 +110,17 @@ public:
    * @returns If schema is a draft version - then one of the default
    * vocabularies, else a user-schema is loaded.
    */
-  Vocabulary<A> const & vocab(URI schema) {
-    if (auto it = g_schema_ids.find(schema.resource()); it != g_schema_ids.end()) {
+  Vocabulary<A> const & vocab(URI const & schema) {
+    static std::map<std::string_view, schema::Version> const schema_ids{
+        {"json-schema.org/draft-03/schema", schema::Version::Draft03},
+        {"json-schema.org/draft-04/schema", schema::Version::Draft04},
+        {"json-schema.org/draft-06/schema", schema::Version::Draft06},
+        {"json-schema.org/draft-07/schema", schema::Version::Draft07},
+        {"json-schema.org/draft/2019-09/schema", schema::Version::Draft2019_09},
+        {"json-schema.org/draft/2020-12/schema", schema::Version::Draft2020_12},
+    };
+
+    if (auto it = schema_ids.find(schema.resource()); it != schema_ids.end()) {
       return vocab(it->second);
     }
 
@@ -252,8 +258,8 @@ public:
       if (not uri.is_relative()) {
         return base.root() / uri;
       }
-      if (auto br = base.resource(), ur = uri.resource();
-          br.ends_with(ur) && br[br.size() - ur.size() - 1] == '/') {
+      if (auto base_rsrc = base.resource(), u_rsrc = uri.resource();
+          base_rsrc.ends_with(u_rsrc) && base_rsrc[base_rsrc.size() - u_rsrc.size() - 1] == '/') {
         return base;
       }
       return base.parent() / uri;
@@ -390,12 +396,12 @@ private:
    * @param json The document being primed.
    */
   void prime_roots(Reference & where, schema::Version version, A const & json) {
-    std::string const id = version <= schema::Version::Draft04 ? "id" : "$id";
+    std::string const id_token = version <= schema::Version::Draft04 ? "id" : "$id";
     auto const schema = json.as_object();
 
     RootReference root = where.root();
-    if (schema.contains(id)) {
-      root = RootReference(schema[id].as_string());
+    if (schema.contains(id_token)) {
+      root = RootReference(schema[id_token].as_string());
       if (root.uri().empty()) {
         root = RootReference(where.uri(), root.anchor());
       } else if (not root.uri().is_rootless() || where.uri().empty()) {
@@ -425,7 +431,7 @@ private:
     // handling mechanisms, so it is not convenient to merge together
     if (version == schema::Version::Draft2019_09 && schema.contains("$recursiveAnchor") &&
         schema["$recursiveAnchor"].as_boolean()) {
-      Anchor anchor;
+      Anchor const anchor;
       root = RootReference(root.uri(), anchor);
 
       roots_.emplace(root, json);
@@ -438,7 +444,7 @@ private:
     }
 
     if (schema.contains("$dynamicAnchor") && version > schema::Version::Draft2019_09) {
-      Anchor anchor(schema["$dynamicAnchor"].as_string());
+      Anchor const anchor(schema["$dynamicAnchor"].as_string());
       root = RootReference(root.uri(), anchor);
 
       roots_.emplace(root, json);
@@ -465,9 +471,10 @@ private:
     std::unordered_map<std::string, bool> keywords;
     std::unordered_set<std::string> vocab_docs;
     for (auto [vocab, required] : vocabularies) {
-      size_t n = vocab.find("/vocab/");
-      vocab_docs.emplace(vocab.substr(n));
-      vocab.replace(n, 7, "/meta/");
+      constexpr std::string_view vocab_div = "/vocab/";
+      size_t const pos = vocab.find(vocab_div);
+      vocab_docs.emplace(vocab.substr(pos));
+      vocab.replace(pos, vocab_div.size(), "/meta/");
 
       std::string error;
       auto vocab_object = external_.try_load(URI(vocab), error);

+ 5 - 3
include/jvalidate/detail/relative_pointer.h

@@ -1,8 +1,10 @@
 #pragma once
 
+#include <cstdlib>
 #include <ostream>
 #include <string>
 #include <string_view>
+#include <variant>
 
 #include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/number.h>
@@ -26,12 +28,12 @@ namespace jvalidate::detail {
  */
 class RelativePointer {
 public:
-  RelativePointer(std::string_view path) {
+  explicit(false) RelativePointer(std::string_view path) {
     if (path == "0") { // A literal RelativePointer of "0" simply means "here"
       return;
     }
 
-    if (size_t pos = path.find('/'); pos != path.npos) {
+    if (size_t const pos = path.find('/'); pos != std::string_view::npos) {
       // Handle the JSON-Pointer version
       pointer_ = Pointer(path.substr(pos));
       path.remove_suffix(path.size() - pos);
@@ -79,6 +81,6 @@ public:
 private:
   size_t parent_steps_ = 0;
   bool requests_key_ = false;
-  Pointer pointer_ = {};
+  Pointer pointer_;
 };
 }

+ 13 - 0
include/jvalidate/detail/scoped_state.h

@@ -2,6 +2,7 @@
 
 #include <functional>
 #include <type_traits>
+#include <utility>
 
 #include <jvalidate/_macro.h>
 
@@ -73,6 +74,18 @@ public:
     prop = std::move(value);
   }
 
+  ScopedState(ScopedState const &) = delete;
+  ScopedState & operator=(ScopedState const &) = delete;
+
+  // Must be explicity implemented because function's move ctor is non-destructive
+  ScopedState(ScopedState && other) noexcept { std::swap(other.reset_, reset_); }
+
+  // Must be explicity implemented because function's move-assign is non-destructive
+  ScopedState & operator=(ScopedState && other) noexcept {
+    std::swap(other.reset_, reset_);
+    return *this;
+  }
+
   ~ScopedState() { reset_(); }
 
   /**

+ 21 - 13
include/jvalidate/detail/simple_adapter.h

@@ -1,6 +1,14 @@
 #pragma once
 
+#include <compare>
+#include <concepts>
+#include <cstdlib>
 #include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
 #include <vector>
 
 #include <jvalidate/adapter.h>
@@ -24,12 +32,12 @@ public:
   using underlying_iterator_t = decltype(std::declval<JSON>().begin());
   using const_iterator = JsonObjectIterator<underlying_iterator_t, Adapter>;
 
-  SimpleObjectAdapter(JSON * value) : value_(value) {}
+  explicit(false) SimpleObjectAdapter(JSON * value) : value_(value) {}
 
   size_t size() const { return value_ ? value_->size() : 0; }
 
   const_iterator find(std::string const & key) const {
-    return std::find_if(begin(), end(), [&key](auto const & kv) { return kv.first == key; });
+    return std::find_if(begin(), end(), [&key](auto const & pair) { return pair.first == key; });
   }
 
   bool contains(std::string const & key) const { return find(key) != end(); }
@@ -71,7 +79,7 @@ public:
   using underlying_iterator_t = decltype(std::declval<JSON>().begin());
   using const_iterator = JsonArrayIterator<underlying_iterator_t, Adapter>;
 
-  SimpleArrayAdapter(JSON * value) : value_(value) {}
+  explicit(false) SimpleArrayAdapter(JSON * value) : value_(value) {}
 
   size_t size() const { return value_ ? value_->size() : 0; }
 
@@ -131,12 +139,12 @@ private:
  */
 template <typename JSON, typename CRTP = AdapterFor<JSON>> class SimpleAdapter : public Adapter {
 public:
-  static constexpr bool is_mutable = not std::is_const_v<JSON> && HasMutableObject<CRTP>;
+  static bool constexpr is_mutable = not std::is_const_v<JSON> && HasMutableObject<CRTP>;
   using value_type = std::remove_const_t<JSON>;
 
 public:
-  SimpleAdapter(JSON * value = nullptr) : value_(value) {}
-  SimpleAdapter(JSON & value) : value_(&value) {}
+  explicit(false) SimpleAdapter(JSON * value = nullptr) : value_(value) {}
+  explicit(false) SimpleAdapter(JSON & value) : value_(&value) {}
 
   size_t array_size() const final { return self().as_array().size(); }
 
@@ -144,10 +152,10 @@ public:
 
   detail::SimpleArrayAdapter<JSON> as_array() const { return value_; }
 
-  Status apply_array(AdapterCallback const & cb) const final {
+  Status apply_array(AdapterCallback const & callback) const final {
     Status result = Status::Noop;
     for (auto const & child : self().as_array()) {
-      result = cb(child) & result;
+      result = callback(child) & result;
     }
     return result;
   }
@@ -160,10 +168,10 @@ public:
 
   detail::SimpleObjectAdapter<JSON, CRTP> as_object() const { return value_; }
 
-  Status apply_object(ObjectAdapterCallback const & cb) const final {
+  Status apply_object(ObjectAdapterCallback const & callback) const final {
     Status result = Status::Noop;
     for (auto const & [key, child] : self().as_object()) {
-      result = cb(key, child) & result;
+      result = callback(key, child) & result;
     }
     return result;
   }
@@ -173,7 +181,7 @@ public:
   }
 
   void assign(Const const & frozen) const
-    requires(is_mutable)
+    requires is_mutable
   {
     EXPECT_M(value_ != nullptr, "Failed to create a new element in parent object");
     if (auto * this_frozen = dynamic_cast<GenericConst<value_type> const *>(&frozen)) {
@@ -199,7 +207,7 @@ public:
     if (lhs.value_ == rhs.value_) {
       return ord::equal;
     }
-    // TODO: Can I implement this as `return *value_ <=> *rhs.value_`?
+    // TODO(samjaffe): Can I implement this as `return *value_ <=> *rhs.value_`?
     if (lhs.value_ && rhs.value_) {
       if (*lhs.value_ < *rhs.value_) {
         return ord::less;
@@ -216,7 +224,7 @@ public:
     return rhs.type() == Type::Null ? ord::equivalent : ord::less;
   }
 
-  bool equals(Adapter const & rhs, bool strict = false) const final {
+  bool equals(Adapter const & rhs, bool strict) const final {
     Type const rhs_type = rhs.type();
 
     switch (rhs_type) {

+ 3 - 3
include/jvalidate/detail/string.h

@@ -3,14 +3,14 @@
  * std::string/std::regex is not well suited for UTF8 comprehensions.
  */
 #pragma once
-#include <jvalidate/_config.h>
+#include <jvalidate/_config.h> // IWYU pragma: keep
 
 #include <cstring>
 #include <memory>
 #include <string_view>
 
 #if JVALIDATE_HAS_ICU
-#include <unicode/brkiter.h>
+#include <unicode/stringpiece.h>
 #include <unicode/unistr.h>
 #endif
 
@@ -63,7 +63,7 @@ inline std::u32string to_u32(std::string_view str) {
  * graphemes/codepoints in the string.
  */
 inline size_t length(std::string_view arg) {
-  icu::UnicodeString ucs = icu::UnicodeString::fromUTF8(icu::StringPiece(arg));
+  icu::UnicodeString const ucs = icu::UnicodeString::fromUTF8(icu::StringPiece(arg));
   return ucs.countChar32();
 }
 #else

+ 21 - 15
include/jvalidate/detail/string_adapter.h

@@ -1,14 +1,18 @@
 #pragma once
 
+#include <cstdint>
+#include <cstdlib>
 #include <map>
+#include <memory>
+#include <optional>
 #include <stdexcept>
+#include <string>
 #include <string_view>
 #include <vector>
 
 #include <jvalidate/adapter.h>
-#include <jvalidate/detail/number.h>
-#include <jvalidate/detail/simple_adapter.h>
 #include <jvalidate/enum.h>
+#include <jvalidate/forward.h>
 #include <jvalidate/status.h>
 
 namespace jvalidate::detail {
@@ -57,24 +61,26 @@ class StringAdapter final : public adapter::Adapter {
 public:
   using value_type = std::string_view;
 
-  StringAdapter(std::string_view value) : value_(value) {}
+  explicit(false) StringAdapter(std::string_view value) : value_(value) {}
 
-  adapter::Type type() const { return adapter::Type::String; }
-  bool as_boolean() const { die("boolean"); }
-  int64_t as_integer() const { die("integer"); }
-  double as_number() const { die("number"); }
-  std::string as_string() const { return std::string(value_); }
+  adapter::Type type() const final { return adapter::Type::String; }
+  bool as_boolean() const final { die("boolean"); }
+  int64_t as_integer() const final { die("integer"); }
+  double as_number() const final { die("number"); }
+  std::string as_string() const final { return std::string(value_); }
 
-  size_t array_size() const { die("array"); }
+  size_t array_size() const final { die("array"); }
+  // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
   UnsupportedArrayAdapter<StringAdapter> as_array() const { die("array"); }
-  Status apply_array(adapter::AdapterCallback const &) const { return Status::Noop; }
+  Status apply_array(adapter::AdapterCallback const &) const final { return Status::Noop; }
 
-  size_t object_size() const { die("object"); }
+  size_t object_size() const final { die("object"); }
+  // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
   UnsupportedObjectAdapter<StringAdapter> as_object() const { die("object"); }
-  Status apply_object(adapter::ObjectAdapterCallback const &) const { return Status::Noop; }
+  Status apply_object(adapter::ObjectAdapterCallback const &) const final { return Status::Noop; }
 
-  bool equals(adapter::Adapter const & rhs, bool strict) const {
-    if (std::optional str = rhs.maybe_string(strict)) {
+  bool equals(adapter::Adapter const & rhs, bool strict) const final {
+    if (std::optional const str = rhs.maybe_string(strict)) {
       return str == value_;
     }
     return false;
@@ -85,7 +91,7 @@ public:
   }
 
 private:
-  [[noreturn]] static void die(std::string expected) {
+  [[noreturn]] static void die(std::string const & expected) {
     throw std::runtime_error("StringAdapter is not an " + expected);
   }
 

+ 4 - 4
include/jvalidate/detail/tribool.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include <compare>
+#include <compare> // IWYU pragma: keep
 
 /**
  * @brief Generator-macro for creating instant tri-bools, a boolean-like type
@@ -41,15 +41,15 @@
 #define JVALIDATE_TRIBOOL_TYPE(TypeName, True, False, Maybe)                                       \
   class TypeName {                                                                                 \
   public:                                                                                          \
-    enum Enum { True, False, Maybe };                                                              \
+    enum Enum : int8_t { True, False, Maybe };                                                     \
                                                                                                    \
   private:                                                                                         \
     Enum state_;                                                                                   \
                                                                                                    \
   public:                                                                                          \
     /* Translate a boolean into a tribool value, will never be Maybe */                            \
-    constexpr TypeName(bool state) : state_(state ? True : False) {}                               \
-    constexpr TypeName(Enum state) : state_(state) {}                                              \
+    constexpr explicit(false) TypeName(bool state) : state_(state ? True : False) {}               \
+    constexpr explicit(false) TypeName(Enum state) : state_(state) {}                              \
                                                                                                    \
     /* Convert to enum for use in switch() statements */                                           \
     constexpr Enum operator*() const { return state_; }                                            \

+ 3 - 1
include/jvalidate/detail/vocabulary.h

@@ -1,9 +1,11 @@
 #pragma once
 
+#include <string>
 #include <string_view>
 #include <unordered_map>
 #include <unordered_set>
 
+#include <jvalidate/detail/expect.h>
 #include <jvalidate/enum.h>
 #include <jvalidate/forward.h>
 #include <jvalidate/vocabulary.h>
@@ -13,7 +15,7 @@ template <Adapter A> struct ParserContext;
 
 template <Adapter A> class Vocabulary {
 private:
-  schema::Version version_;
+  schema::Version version_ = schema::Version::Draft2020_12;
   std::unordered_map<std::string_view, vocabulary::Metadata<A>> metadata_;
   std::unordered_set<std::string_view> permitted_;
   std::unordered_set<std::string> vocabularies_;

+ 4 - 4
include/jvalidate/document_cache.h

@@ -2,8 +2,8 @@
 
 #include <map>
 #include <optional>
+#include <string>
 
-#include <jvalidate/detail/expect.h>
 #include <jvalidate/forward.h>
 #include <jvalidate/uri.h>
 
@@ -26,7 +26,7 @@ namespace jvalidate {
  */
 template <Adapter A> class DocumentCache {
 public:
-  using value_type = typename A::value_type;
+  using value_type = A::value_type;
 
 private:
   URIResolver<A> resolve_;
@@ -52,9 +52,9 @@ public:
    * @see tests/selfvalidate_test.cxx#load_external_for_test for an example
    * supporting http requests with libcurl and file requests with fstreams.
    */
-  DocumentCache(URIResolver<A> const & resolve) : resolve_(resolve) {}
+  explicit(false) DocumentCache(URIResolver<A> const & resolve) : resolve_(resolve) {}
 
-  operator bool() const { return resolve_; }
+  explicit operator bool() const { return resolve_; }
 
   std::optional<A> try_load(URI const & uri, std::string & error) {
     // Short circuit - without a URIResolver, we can always return nullopt,

+ 2 - 1
include/jvalidate/enum.h

@@ -1,4 +1,5 @@
 #pragma once
+#include <cstdint>
 
 #include <jvalidate/forward.h>
 
@@ -15,7 +16,7 @@ enum class Type : int8_t {
 }
 
 namespace jvalidate::schema {
-enum class Version : int {
+enum class Version : int8_t { // NOLINT(readability-enum-initial-value)
   // Keywords: type, properties, patternProperties, additionalProperties, items, additionalItems
   //           required, dependencies, minimum, maximum, exclusiveMinimum, exclusiveMaximum,
   //           minItems, maxItems, uniqueItems, pattern, minLength, maxLength, enum, default,

+ 84 - 54
include/jvalidate/format.h

@@ -1,10 +1,22 @@
 #pragma once
+#include <jvalidate/_config.h>
+#include <jvalidate/_macro.h>
+
+/*
+ NOLINTBEGIN(readability-identifier-length,
+             bugprone-inc-dec-in-conditions,
+             cppcoreguidelines-avoid-magic-numbers,
+             bugprone-suspicious-stringview-data-usage,
+             readability-implicit-bool-conversion,
+             cppcoreguidelines-narrowing-conversions,
+             readability-identifier-length)
+ */
+#include <cstdint>
 #include <cstdio>
 #include <functional>
-#include <jvalidate/_macro.h>
 
 #include <cctype>
-#include <chrono>
+#include <chrono> // IWYU pragma: keep
 #include <cstddef>
 #include <cstring>
 #include <ctime>
@@ -62,7 +74,7 @@ inline bool is_hex(std::string_view s) {
   return s.find_first_not_of(g_hex_digits) == std::string::npos;
 }
 
-struct result {
+struct Result {
   ptrdiff_t consumed;
   bool valid;
 };
@@ -70,16 +82,16 @@ struct result {
 inline bool is_leapyear(int y) { return (y % 400) == 0 || ((y % 4) == 0 && (y % 100) != 0); }
 
 inline bool illegal_date(int y, int m, int d) {
-  static constexpr int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+  static constexpr std::array<int, 12> days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
   if (is_leapyear(y) && m == 1) {
     --d;
   }
-  return d > days[m];
+  return m >= 0 && m < days.size() && d > days.at(m);
 }
 
-inline result date(std::string_view dt) {
+inline Result date(std::string_view dt) {
   struct tm tm;
-  if (auto end = strptime(dt.data(), "%Y-%m-%d", &tm); end) {
+  if (char * end = strptime(dt.data(), "%Y-%m-%d", &tm); end) {
     if ((end - dt.data()) != 10 || illegal_date(tm.tm_year + 1900, tm.tm_mon, tm.tm_mday)) {
       return {.consumed = 0, .valid = false};
     }
@@ -90,7 +102,7 @@ inline result date(std::string_view dt) {
 
 inline bool is_leapsecond(std::tm tm) {
   if (tm.tm_sec != 60) {
-    return true;
+    return true; // NOLINT(readability-simplify-boolean-expr) See below...
   }
 
 #if __cpp_lib_chrono >= 201907L
@@ -129,7 +141,7 @@ inline bool is_pchar(std::basic_string_view<CharT> part, size_t & pos,
     return pos + 2 < part.size() && std::strchr(g_hex_digits, part[++pos]) &&
            std::strchr(g_hex_digits, part[++pos]);
   }
-  return extra_valid_chars.find(part[pos]) != part.npos;
+  return extra_valid_chars.find(part[pos]) != std::basic_string_view<CharT>::npos;
 };
 
 inline bool is_uri_template_literal(std::u32string_view part, size_t & pos) {
@@ -163,7 +175,7 @@ inline bool is_uri_template_expression(std::u32string_view part) {
        part.remove_prefix(std::min(part.size(), pos)), pos = part.find(',')) {
     std::u32string_view varspec = part.substr(0, pos);
     std::u32string_view expand;
-    if (size_t const mod = varspec.find_first_of(U":*"); mod != varspec.npos) {
+    if (size_t const mod = varspec.find_first_of(U":*"); mod != std::u32string_view::npos) {
       expand = varspec.substr(mod + 1);
       varspec.remove_suffix(expand.size() + 1);
     }
@@ -175,7 +187,8 @@ inline bool is_uri_template_expression(std::u32string_view part) {
       return false;
     }
     for (size_t i = 0; i < varspec.size(); ++i) {
-      RETURN_UNLESS(is_uri_template_varchar(varspec, i) || (i > 0 && varspec[i] == '.'), false);
+      JVALIDATE_RETURN_UNLESS(is_uri_template_varchar(varspec, i) || (i > 0 && varspec[i] == '.'),
+                              false);
     }
   }
 
@@ -229,7 +242,7 @@ template <typename CharT> bool test_uri_part(std::basic_string_view<CharT> & uri
   auto part = uri.substr(pos + 1);
   uri = uri.substr(0, pos);
   for (size_t pos = 0; pos < part.size(); ++pos) {
-    RETURN_UNLESS(detail::is_pchar(part, pos, ":@/?"), false);
+    JVALIDATE_RETURN_UNLESS(detail::is_pchar(part, pos, ":@/?"), false);
   }
   return true;
 };
@@ -248,12 +261,12 @@ inline bool time(std::string_view dt) {
 }
 
 inline bool utc_millisec(std::string_view utc) {
-  int64_t itime;
+  int64_t itime = 0;
   if (auto [end, ec] = std::from_chars(utc.begin(), utc.end(), itime);
       ec == std::errc{} && end == utc.end()) {
     return true;
   }
-  double dtime;
+  double dtime = 0.0;
   auto [end, ec] = std::from_chars(utc.begin(), utc.end(), dtime);
   return ec == std::errc{} && end == utc.end();
 }
@@ -279,11 +292,11 @@ inline bool e_123_phone(std::string_view phone) {
   }
   if (phone[0] != '+') {
     constexpr size_t g_usa_phone_tokens = 3;
-    char area[4], head[4], tail[5];
+    char area[4], head[4], tail[5]; // NOLINT
     return sscanf(phone.data(), "(%3s) %3s %4s", area, head, tail) == g_usa_phone_tokens &&
            detail::is_dec(area, 3) && detail::is_dec(head, 3) && detail::is_dec(tail, 4);
   }
-  char tok0[4], tok1[4], tok2[4], tok3[5];
+  char tok0[4], tok1[4], tok2[4], tok3[5]; // NOLINT
   constexpr size_t g_i18n_phone_tokens = 4;
   return sscanf(phone.data(), "+%3s %3s %3s %4s", tok0, tok1, tok2, tok3) == g_i18n_phone_tokens &&
          detail::is_dec(tok0, 1, 3) && detail::is_dec(tok1, 2, 3) && detail::is_dec(tok2, 2, 3) &&
@@ -318,7 +331,8 @@ inline bool time(std::string_view dt) {
     return dt.size() == 1 && detail::is_leapsecond(tm);
   }
   if (std::strchr("+-", dt[0])) {
-    return strptime(dt.data() + 1, "%R", &tm) == dt.end() && detail::is_leapsecond(tm);
+    dt.remove_prefix(1);
+    return strptime(dt.data(), "%R", &tm) == dt.end() && detail::is_leapsecond(tm);
   }
   return false;
 }
@@ -337,28 +351,28 @@ template <typename CharT> inline bool uri(std::basic_string_view<CharT> uri) {
 
   // https://www.rfc-editor.org/rfc/rfc3986.html#appendix-A
   if (size_t const pos = uri.find(':'); pos != uri.npos) {
-    RETURN_UNLESS(std::isalpha(uri[0]), false);
+    JVALIDATE_RETURN_UNLESS(std::isalpha(uri[0]), false);
     for (size_t i = 1; i < pos; ++i) {
-      RETURN_UNLESS(std::isalnum(uri[i]) || std::strchr("+-.", uri[i]), false);
+      JVALIDATE_RETURN_UNLESS(std::isalnum(uri[i]) || std::strchr("+-.", uri[i]), false);
     }
     uri.remove_prefix(pos + 1);
   } else {
     return false;
   }
 
-  RETURN_UNLESS(detail::test_uri_part(uri, '#'), false);
-  RETURN_UNLESS(detail::test_uri_part(uri, '?'), false);
+  JVALIDATE_RETURN_UNLESS(detail::test_uri_part(uri, '#'), false);
+  JVALIDATE_RETURN_UNLESS(detail::test_uri_part(uri, '?'), false);
 
   auto path = uri;
   if (uri.starts_with(delim::double_slash)) {
     uri.remove_prefix(2);
     path = uri.substr(std::min(uri.size(), uri.find('/')));
     uri.remove_suffix(path.size());
-    RETURN_UNLESS(detail::is_uri_authority(uri), false);
+    JVALIDATE_RETURN_UNLESS(detail::is_uri_authority(uri), false);
   }
 
   for (size_t i = 0; i < path.size(); ++i) {
-    RETURN_UNLESS(detail::is_pchar(path, i, "/:@"), false);
+    JVALIDATE_RETURN_UNLESS(detail::is_pchar(path, i, "/:@"), false);
   }
 
   return true;
@@ -370,26 +384,26 @@ template <typename CharT> inline bool uri_reference(std::basic_string_view<CharT
     return true;
   }
 
-  RETURN_UNLESS(detail::test_uri_part(uri, '#'), false);
-  RETURN_UNLESS(detail::test_uri_part(uri, '?'), false);
+  JVALIDATE_RETURN_UNLESS(detail::test_uri_part(uri, '#'), false);
+  JVALIDATE_RETURN_UNLESS(detail::test_uri_part(uri, '?'), false);
 
   auto path = uri;
   if (uri.starts_with(delim::double_slash)) {
     uri.remove_prefix(2);
     path = uri.substr(std::min(uri.size(), uri.find('/')));
     uri.remove_suffix(path.size());
-    RETURN_UNLESS(detail::is_uri_authority(uri), false);
+    JVALIDATE_RETURN_UNLESS(detail::is_uri_authority(uri), false);
   }
 
   if (size_t const pos = path.find('/'); pos != path.npos) {
     for (size_t i = 0; i < pos; ++i) {
-      RETURN_UNLESS(detail::is_pchar(path, i, "@"), false);
+      JVALIDATE_RETURN_UNLESS(detail::is_pchar(path, i, "@"), false);
     }
     path.remove_prefix(pos);
   }
 
   for (size_t i = 0; i < path.size(); ++i) {
-    RETURN_UNLESS(detail::is_pchar(path, i, "/:@"), false);
+    JVALIDATE_RETURN_UNLESS(detail::is_pchar(path, i, "/:@"), false);
   }
 
   return true;
@@ -398,14 +412,14 @@ template <typename CharT> inline bool uri_reference(std::basic_string_view<CharT
 inline bool uri_template(std::u32string_view uri) {
   for (size_t i = 0; i < uri.size(); ++i) {
     if (uri[i] != '{') {
-      RETURN_UNLESS(detail::is_uri_template_literal(uri, i), false);
+      JVALIDATE_RETURN_UNLESS(detail::is_uri_template_literal(uri, i), false);
       continue;
     }
 
     std::u32string_view expr = uri.substr(i + 1);
     size_t const pos = expr.find('}');
-    RETURN_UNLESS(pos != uri.npos, false);
-    RETURN_UNLESS(detail::is_uri_template_expression(expr.substr(0, pos)), false);
+    JVALIDATE_RETURN_IF(pos == std::u32string_view::npos, false);
+    JVALIDATE_RETURN_UNLESS(detail::is_uri_template_expression(expr.substr(0, pos)), false);
     i += pos + 1;
   }
   return true;
@@ -414,9 +428,10 @@ inline bool uri_template(std::u32string_view uri) {
 inline bool uuid(std::string_view id) {
   constexpr size_t g_uuid_len = 36;
   constexpr size_t g_uuid_tokens = 5;
-  char tok0[9], tok1[5], tok2[5], tok3[5], tok4[13];
+  char tok0[9], tok1[5], tok2[5], tok3[5], tok4[13]; // NOLINT
 
   return id.size() == g_uuid_len &&
+         // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
          sscanf(id.data(), "%8s-%4s-%4s-%4s-%12s", tok0, tok1, tok2, tok3, tok4) == g_uuid_tokens &&
          detail::is_hex(tok0) && detail::is_hex(tok1) && detail::is_hex(tok2) &&
          detail::is_hex(tok3) && detail::is_hex(tok4);
@@ -424,8 +439,9 @@ inline bool uuid(std::string_view id) {
 
 inline bool duration(std::string_view dur) {
   auto eat = [&dur](std::string_view text) {
-    char type;
-    unsigned int rep;
+    char type = '\0';
+    unsigned int rep = 0;
+    // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
     if (sscanf(dur.data(), "%u%c", &rep, &type) != 2 || text.find(type) == std::string::npos) {
       return std::string::npos;
     }
@@ -454,8 +470,8 @@ inline bool duration(std::string_view dur) {
     std::string_view ymd{"YMD"};
     // Read YMD duration offsets in that order, allowing us to skip past them.
     while (not ymd.empty() && not dur.empty()) {
-      if (size_t n = eat(ymd); n != std::string::npos) {
-        ymd.remove_prefix(n + 1);
+      if (size_t const pos = eat(ymd); pos != std::string::npos) {
+        ymd.remove_prefix(pos + 1);
       } else {
         return false;
       }
@@ -475,8 +491,8 @@ inline bool duration(std::string_view dur) {
   std::string_view hms{"HMS"};
   // Read HMS duration offsets in that order, allowing us to skip past them.
   while (not hms.empty() && not dur.empty()) {
-    if (size_t n = eat(hms); n != std::string::npos) {
-      hms.remove_prefix(n + 1);
+    if (size_t const pos = eat(hms); pos != std::string::npos) {
+      hms.remove_prefix(pos + 1);
     } else {
       return false;
     }
@@ -561,10 +577,10 @@ template <typename CharT> inline bool hostname(std::basic_string_view<CharT> nam
 }
 
 inline bool ipv4(std::string_view ip) {
-  unsigned int ip0, ip1, ip2, ip3;
-  char eof;
+  unsigned int ip0, ip1, ip2, ip3; // NOLINT
+  char eof = '\0';
   // IPv4 address MAY only contain DIGITS and '.'
-  if (ip.find_first_not_of("0123456789.") != ip.npos) {
+  if (ip.find_first_not_of("0123456789.") != std::string_view::npos) {
     return false;
   }
 
@@ -572,7 +588,8 @@ inline bool ipv4(std::string_view ip) {
   if (ip[0] == '0' && std::isdigit(ip[1])) {
     return false;
   }
-  if (size_t n = ip.find(".0"); n != ip.npos && std::isdigit(ip[n + 2])) {
+  if (size_t const pos = ip.find(".0");
+      pos != std::string_view::npos && std::isdigit(ip[pos + 2])) {
     return false;
   }
 
@@ -621,17 +638,19 @@ inline bool ipv6(std::string_view ip) {
   }
 
   while (!ip.empty() && ++groups) {
-    int data;
+    int data = 0;
     if (sscanf(ip.data(), "%4x", &data) != 1) {
       // Not a 4-byte HEXDIGIT. Not sure that it's ever possible due to the
       // char filter above.
       return false;
     }
 
-    if (size_t const n = ip.find(':'); std::min(n, ip.size()) > 4) {
-      return false; // Segment too wide
-    } else if (n != std::string::npos) {
-      ip.remove_prefix(n + 1);
+    size_t const div_pos = ip.find(':');
+    if (std::min(div_pos, ip.size()) > 4) {
+      return false; // Segments must be between 1 and 4 characters long
+    }
+    if (div_pos != std::string::npos) {
+      ip.remove_prefix(div_pos + 1);
     } else {
       break; // End of String
     }
@@ -666,7 +685,7 @@ template <typename CharT> inline bool email(std::basic_string_view<CharT> em) {
   auto const who = em.substr(0, n);
   if (who.starts_with('"') && who.ends_with('"')) {
     // No validation
-  } else if (who.starts_with('.') || who.ends_with('.')) {
+  } else if (who.starts_with('.') || who.ends_with('.')) { // NOLINT(bugprone-branch-clone)
     return false;
   } else if (em.substr(0, n).find(delim::dotdot) != em.npos) {
     return false;
@@ -687,11 +706,11 @@ template <typename CharT> inline bool email(std::basic_string_view<CharT> em) {
 
   // When the DOMAIN is an IPv6, it must start with "IPv6:" for some
   // weird compatibility reason.
-  if (auto ip = detail::to_u8(domain); ip.starts_with("IPv6:")) {
+  auto ip = detail::to_u8(domain);
+  if (ip.starts_with("IPv6:")) {
     return ipv6(ip.substr(5));
-  } else {
-    return ipv4(ip);
   }
+  return ipv4(ip);
 }
 
 template <typename T> inline bool ctor_as_valid(std::string_view str) {
@@ -714,7 +733,7 @@ public:
   using StatelessPredicate = bool (*)(std::string_view);
   using Predicate = std::function<bool(std::string_view)>;
   using UserDefinedFormats = std::unordered_map<std::string, Predicate>;
-  enum class Status { Unknown, Unimplemented, Valid, Invalid };
+  enum class Status : int8_t { Unknown, Unimplemented, Valid, Invalid };
 
 private:
   // This isn't actually a user format, but we don't generate any special
@@ -766,8 +785,10 @@ private:
 
 public:
   FormatValidator() = default;
-  FormatValidator(Predicate is_regex) { formats_.insert_or_assign("regex", is_regex); }
-  FormatValidator(UserDefinedFormats const & formats, Predicate is_regex) : formats_(formats) {
+  explicit(false) FormatValidator(Predicate is_regex) {
+    formats_.insert_or_assign("regex", is_regex);
+  }
+  FormatValidator(UserDefinedFormats formats, Predicate is_regex) : formats_(std::move(formats)) {
     formats_.insert_or_assign("regex", is_regex);
   }
 
@@ -797,3 +818,12 @@ private:
 
 #undef CONSTRUCTS
 #undef UTF32
+/*
+ NOLINTEND(readability-identifier-length,
+           bugprone-inc-dec-in-conditions,
+           cppcoreguidelines-avoid-magic-numbers,
+           bugprone-suspicious-stringview-data-usage,
+           readability-implicit-bool-conversion,
+           cppcoreguidelines-narrowing-conversions,
+           readability-identifier-length)
+ */

+ 39 - 33
include/jvalidate/forward.h

@@ -1,11 +1,16 @@
 #pragma once
 
 #include <concepts>
+#include <cstdint>
+#include <cstdlib>
 #include <functional>
 #include <iosfwd>
+#include <iterator>
+#include <memory>
 #include <string>
 #include <string_view>
 #include <type_traits>
+#include <utility>
 #include <variant>
 
 #define DISCARD1_IMPL(_, ...) __VA_ARGS__
@@ -40,13 +45,13 @@ using ObjectAdapterCallback = std::function<Status(std::string const &, adapter:
 template <typename> struct AdapterTraits;
 template <typename V> struct AdapterTraits<V const> : AdapterTraits<V> {};
 
-template <typename JSON> using AdapterFor = typename AdapterTraits<JSON>::template Adapter<JSON>;
+template <typename JSON> using AdapterFor = AdapterTraits<JSON>::template Adapter<JSON>;
 template <typename JSON>
 bool load_stream(std::istream & in, JSON & out, std::string & error) noexcept;
 }
 
 namespace jvalidate::schema {
-enum class Version : int;
+enum class Version : int8_t;
 class Node;
 }
 
@@ -112,20 +117,20 @@ concept ObjectIterator =
     };
 
 template <typename A, typename Reentrant>
-concept ArrayAdapter = requires(A const a) {
-  { a.size() } -> std::convertible_to<std::size_t>;
-  { a[0UL] } -> std::same_as<Reentrant>;
-  { a.begin() } -> ArrayIterator;
-  { a.end() } -> ArrayIterator;
+concept ArrayAdapter = requires(A const adapter) {
+  { adapter.size() } -> std::convertible_to<size_t>;
+  { adapter[0UL] } -> std::same_as<Reentrant>;
+  { adapter.begin() } -> ArrayIterator;
+  { adapter.end() } -> ArrayIterator;
 };
 
 template <typename A, typename Reentrant>
-concept ObjectAdapter = requires(A const a) {
-  { a.size() } -> std::convertible_to<std::size_t>;
-  { a.contains("") } -> std::same_as<bool>;
-  { a[""] } -> std::same_as<Reentrant>;
-  { a.begin() } -> ObjectIterator;
-  { a.end() } -> ObjectIterator;
+concept ObjectAdapter = requires(A const adapter) {
+  { adapter.size() } -> std::convertible_to<size_t>;
+  { adapter.contains("") } -> std::same_as<bool>;
+  { adapter[""] } -> std::same_as<Reentrant>;
+  { adapter.begin() } -> ObjectIterator;
+  { adapter.end() } -> ObjectIterator;
 };
 
 /**
@@ -139,23 +144,23 @@ concept ObjectAdapter = requires(A const a) {
  * an as_array and as_object method added on.
  */
 template <typename A>
-concept Adapter = std::is_base_of_v<adapter::Adapter, A> && requires(A const a) {
+concept Adapter = std::is_base_of_v<adapter::Adapter, A> && requires(A const adapter) {
   typename A::value_type;
-  { a.type() } -> std::same_as<adapter::Type>;
-  { a.as_boolean() } -> std::same_as<bool>;
-  { a.as_integer() } -> std::convertible_to<int64_t>;
-  { a.as_number() } -> std::convertible_to<double>;
-  { a.as_number() } -> std::floating_point;
-  { a.as_object() } -> ObjectAdapter<A>;
-  { a.as_array() } -> ArrayAdapter<A>;
-
-  { a.array_size() } -> std::convertible_to<size_t>;
-  { a.object_size() } -> std::convertible_to<size_t>;
+  { adapter.type() } -> std::same_as<adapter::Type>;
+  { adapter.as_boolean() } -> std::same_as<bool>;
+  { adapter.as_integer() } -> std::convertible_to<int64_t>;
+  { adapter.as_number() } -> std::convertible_to<double>;
+  { adapter.as_number() } -> std::floating_point;
+  { adapter.as_object() } -> ObjectAdapter<A>;
+  { adapter.as_array() } -> ArrayAdapter<A>;
+
+  { adapter.array_size() } -> std::convertible_to<size_t>;
+  { adapter.object_size() } -> std::convertible_to<size_t>;
 };
 
 template <typename A, typename Reentrant>
-concept MutableObject = ObjectAdapter<A, Reentrant> && requires(A const a) {
-  { a.assign("", std::declval<adapter::Const>()) };
+concept MutableObject = ObjectAdapter<A, Reentrant> && requires(A const adapter) {
+  { adapter.assign("", std::declval<adapter::Const>()) };
 };
 
 template <typename A>
@@ -171,10 +176,10 @@ concept HasMutableObject = MutableObject<decltype(std::declval<A>().as_object())
  * Implies that as_array is also a wrapper for mutable JSON.
  */
 template <typename A>
-concept MutableAdapter = Adapter<A> && requires(A const a) {
-  { a.assign(std::declval<adapter::Const>()) };
-  { a.assign(std::declval<adapter::Adapter>()) };
-  { a.as_object() } -> MutableObject<A>;
+concept MutableAdapter = Adapter<A> && requires(A const adapter) {
+  { adapter.assign(std::declval<adapter::Const>()) };
+  { adapter.assign(std::declval<adapter::Adapter>()) };
+  { adapter.as_object() } -> MutableObject<A>;
 };
 
 template <typename R>
@@ -185,9 +190,10 @@ concept RegexEngine = requires(R & engine) {
 };
 
 template <typename E, typename A, typename B, typename V>
-concept Visitable = Adapter<A> && requires(V & v, E const & c, A const & doc, B const & base) {
-  { v.visit(c, doc, base) } -> std::same_as<Status>;
-};
+concept Visitable =
+    Adapter<A> && requires(V & visitor, E const & constraint, A const & doc, B const & base) {
+      { visitor.visit(constraint, doc, base) } -> std::same_as<Status>;
+    };
 
 template <typename T, typename S>
 concept Not = not std::is_same_v<std::decay_t<T>, std::decay_t<S>>;

+ 18 - 12
include/jvalidate/regex.h

@@ -1,13 +1,20 @@
 #pragma once
+// NOLINTBEGIN(readability-implicit-bool-conversion)
 
+#include <exception> // IWYU pragma: keep
+#include <memory>    // IWYU pragma: keep
 #include <regex>
+#include <string>
+#include <string_view>
 #include <unordered_map>
 
 #include <jvalidate/_macro.h>
 
 #if JVALIDATE_HAS_ICU
+#include <unicode/parseerr.h>
 #include <unicode/regex.h>
-#include <unicode/ustring.h>
+#include <unicode/stringpiece.h>
+#include <unicode/unistr.h>
 #include <unicode/utypes.h>
 #endif
 
@@ -42,8 +49,8 @@ public:
   } catch (std::exception const &) { return false; }
 
   bool search(std::string const & regex, std::string const & text) try {
-    std::regex const & re = cache_.try_emplace(regex, regex).first->second;
-    return std::regex_search(text, re);
+    std::regex const & rexpr = cache_.try_emplace(regex, regex).first->second;
+    return std::regex_search(text, rexpr);
   } catch (std::exception const &) { return false; }
 };
 }
@@ -81,8 +88,8 @@ public:
     icu::UnicodeString const ucs = icu::UnicodeString::fromUTF8(icu::StringPiece(regex));
 
     UErrorCode status = U_ZERO_ERROR;
-    UParseError pe;
-    std::unique_ptr<icu::RegexPattern> tmp(icu::RegexPattern::compile(ucs, pe, status));
+    UParseError perr;
+    std::unique_ptr<icu::RegexPattern> const tmp(icu::RegexPattern::compile(ucs, perr, status));
 
     return not U_FAILURE(status);
   }
@@ -93,11 +100,11 @@ public:
       icu::UnicodeString const ucs = icu::UnicodeString::fromUTF8(icu::StringPiece(regex));
 
       UErrorCode status = U_ZERO_ERROR;
-      UParseError pe;
-      it->second.reset(icu::RegexPattern::compile(ucs, pe, status));
+      UParseError perr;
+      it->second.reset(icu::RegexPattern::compile(ucs, perr, status));
 
       if (U_FAILURE(status)) {
-        // TODO: Provide info?
+        // TODO(samjaffe): Provide info?
         return false;
       }
     }
@@ -109,12 +116,11 @@ public:
     UErrorCode status = U_ZERO_ERROR;
     icu::UnicodeString const ucs = icu::UnicodeString::fromUTF8(icu::StringPiece(text));
     std::unique_ptr<icu::RegexMatcher> matcher(it->second->matcher(ucs, status));
-    if (U_FAILURE(status)) {
-      // Realistically, never called
-      return false;
-    }
+
+    JVALIDATE_RETURN_IF(U_FAILURE(status), false); // Doesn't appear possilbe
     return matcher->find(status);
   }
 };
 }
 #endif
+// NOLINTEND(readability-implicit-bool-conversion)

+ 14 - 10
include/jvalidate/schema.h

@@ -1,15 +1,17 @@
 #pragma once
 
+#include <map>
 #include <memory>
+#include <optional>
+#include <stdexcept> // IWYU pragma: keep
+#include <string>
 #include <unordered_map>
 
 #include <jvalidate/adapter.h>
 #include <jvalidate/constraint.h>
-#include <jvalidate/detail/anchor.h>
 #include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/on_block_exit.h>
 #include <jvalidate/detail/parser_context.h>
-#include <jvalidate/detail/pointer.h>
 #include <jvalidate/detail/reference.h>
 #include <jvalidate/detail/reference_manager.h>
 #include <jvalidate/document_cache.h>
@@ -36,9 +38,9 @@ private:
   std::optional<std::string> rejects_all_;
 
   // Actual constraint information
-  std::optional<schema::Node const *> reference_{};
-  std::unordered_map<std::string, std::unique_ptr<constraint::Constraint>> constraints_{};
-  std::unordered_map<std::string, std::unique_ptr<constraint::Constraint>> post_constraints_{};
+  std::optional<schema::Node const *> reference_;
+  std::unordered_map<std::string, std::unique_ptr<constraint::Constraint>> constraints_;
+  std::unordered_map<std::string, std::unique_ptr<constraint::Constraint>> post_constraints_;
 
 public:
   Node() = default;
@@ -49,7 +51,7 @@ public:
    * Depending on the compiler settings, this might be used to indicate things
    * such as attempting to load a non-existant schema.
    */
-  Node(std::string const & rejection_reason) : rejects_all_(rejection_reason) {}
+  explicit Node(std::string const & rejection_reason) : rejects_all_(rejection_reason) {}
 
   /**
    * @brief Actually initialize this schema node. Unfortunately, we cannot use
@@ -180,6 +182,7 @@ public:
    * be reused.
    */
   template <Adapter A, typename... Args>
+  // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
   Schema(A const & json, schema::Version version, DocumentCache<A> && external, Args &&... args)
       : Schema(json, version, external, std::forward<Args>(args)...) {}
 
@@ -265,8 +268,9 @@ private:
   template <Adapter A>
   schema::Node const * resolve(detail::Reference const & ref,
                                detail::ParserContext<A> const & context, bool dynamic_reference) {
-    detail::Reference lexical = context.ref.canonicalize(ref, context.where, dynamic_reference);
-    detail::Reference dynamic = dynamic_reference ? lexical : context.dynamic_where / "$ref";
+    detail::Reference const lexical =
+        context.ref.canonicalize(ref, context.where, dynamic_reference);
+    detail::Reference const dynamic = dynamic_reference ? lexical : context.dynamic_where / "$ref";
 
     detail::OnBlockExit scope;
     if (lexical.uri() != context.where.uri()) {
@@ -368,7 +372,7 @@ template <Adapter A> bool Node::resolve_reference(detail::ParserContext<A> const
   auto const schema = context.schema.as_object();
 
   if (schema.contains("$ref")) {
-    detail::Reference ref(schema["$ref"].as_string());
+    detail::Reference const ref(schema["$ref"].as_string());
 
     reference_ = context.root.resolve(ref, context, false);
     return true;
@@ -383,7 +387,7 @@ template <Adapter A> bool Node::resolve_reference(detail::ParserContext<A> const
   std::string const dyn_ref =
       context.vocab->version() > schema::Version::Draft2019_09 ? "$dynamicRef" : "$recursiveRef";
   if (schema.contains(dyn_ref)) {
-    detail::Reference ref(schema[dyn_ref].as_string());
+    detail::Reference const ref(schema[dyn_ref].as_string());
 
     reference_ = context.root.resolve(ref, context, true);
     return true;

+ 8 - 7
include/jvalidate/uri.h

@@ -1,10 +1,11 @@
 #pragma once
 
+#include <cstdlib>
+#include <ostream>
 #include <string>
 #include <string_view>
 
-#include <jvalidate/compat/compare.h>
-#include <jvalidate/detail/expect.h>
+#include <jvalidate/compat/compare.h> // IWYU pragma: keep
 
 namespace jvalidate {
 /**
@@ -42,15 +43,15 @@ public:
     }
 
     // Locate file://, http://, and https:// schemes
-    if (size_t n = uri_.find("://"); n != std::string::npos) {
-      scheme_ = n;
-      resource_ = n + 3;
+    if (size_t pos = uri_.find("://"); pos != std::string::npos) {
+      scheme_ = pos;
+      resource_ = pos + 3;
     } else if (uri_.starts_with("urn:")) {
       // Note that we skip over the first colon, because the format of a URN
       // token is "urn:format:data" - and therefore we want the scheme to be
       // "urn:format", with the resource element to be "data".
-      n = uri_.find(':', 4);
-      scheme_ = n;
+      pos = uri_.find(':', 4);
+      scheme_ = pos;
       resource_ = scheme_ + 1;
     }
   }

+ 0 - 2
include/jvalidate/validation_config.h

@@ -1,7 +1,5 @@
 #pragma once
 
-#include <jvalidate/forward.h>
-
 namespace jvalidate {
 struct ValidationConfig {
   // This property controls our willingness to engage in type coercion

+ 15 - 22
include/jvalidate/validation_result.h

@@ -1,7 +1,10 @@
 #pragma once
 
+#include <algorithm>
+#include <cstdlib>
 #include <map>
 #include <ostream>
+#include <string>
 #include <utility>
 #include <variant>
 #include <vector>
@@ -41,17 +44,7 @@ public:
     std::map<std::string, Annotation> annotations;
   };
 
-  struct indent {
-    indent(int i) : i(i) {}
-
-    friend std::ostream & operator<<(std::ostream & os, indent id) {
-      while (id.i-- > 0)
-        os << "  ";
-      return os;
-    }
-
-    int i;
-  };
+  static auto indent(size_t depth) { return std::string(depth * 2, ' '); }
 
 private:
   bool valid_;
@@ -111,27 +104,27 @@ public:
   }
 
   static void print(std::ostream & os, std::map<std::string, Annotation> const & named,
-                    std::string_view name, int const i) {
+                    std::string_view name, int const depth) {
     if (named.empty()) {
       return;
     }
     os << ',' << '\n';
-    os << indent(i) << '"' << name << '"' << ':' << ' ' << '{' << '\n';
+    os << indent(depth) << '"' << name << '"' << ':' << ' ' << '{' << '\n';
     char const * odiv = "";
     for (auto const & [key, anno] : named) {
-      os << std::exchange(odiv, ",\n") << indent(i + 1) << '"' << key << '"' << ':' << ' ';
+      os << std::exchange(odiv, ",\n") << indent(depth + 1) << '"' << key << '"' << ':' << ' ';
       if (auto const * str = std::get_if<0>(&anno)) {
         os << '"' << *str << '"';
-      } else if (auto const * vec = std::get_if<1>(&anno)) {
+      } else if (std::vector<std::string> const * vec = std::get_if<1>(&anno)) {
         os << '[';
         char const * div = "\n";
-        for (size_t n = 0; n < vec->size(); ++n) {
-          os << std::exchange(div, ",\n") << indent(i + 2) << '"' << vec->at(n) << '"';
+        for (std::string const & elem : *vec) {
+          os << std::exchange(div, ",\n") << indent(depth + 2) << '"' << elem << '"';
         }
-        os << '\n' << indent(i + 1) << ']';
+        os << '\n' << indent(depth + 1) << ']';
       }
     }
-    os << '\n' << indent(i) << '}';
+    os << '\n' << indent(depth) << '}';
   }
 
   bool valid() const { return valid_; }
@@ -237,7 +230,7 @@ private:
    * @param result The ValidationResult being consumed
    */
   void merge(ValidationResult && result) & {
-    for (auto && [where, by_schema] : result.results_) {
+    for (auto && [where, by_schema] : std::move(result).results_) {
       for (auto && [schema_path, local] : by_schema) {
         results_[where][schema_path].valid &= local.valid;
         results_[where][schema_path].annotations.merge(local.annotations);
@@ -276,7 +269,7 @@ private:
    */
   void error(detail::Pointer const & where, detail::Pointer const & schema_path,
              std::string const & name, Annotation message) {
-    if (std::visit([](auto const & v) { return v.empty(); }, message)) {
+    if (std::visit([](auto const & val) { return val.empty(); }, message)) {
       return;
     }
     std::visit([](auto & msg) { sanitize(msg); }, message);
@@ -294,7 +287,7 @@ private:
    */
   void annotate(detail::Pointer const & where, detail::Pointer const & schema_path,
                 std::string const & name, Annotation message) {
-    if (std::visit([](auto const & v) { return v.empty(); }, message)) {
+    if (std::visit([](auto const & val) { return val.empty(); }, message)) {
       return;
     }
     std::visit([](auto & msg) { sanitize(msg); }, message);

+ 52 - 40
include/jvalidate/validation_visitor.h

@@ -1,22 +1,35 @@
 #pragma once
 
 #include <algorithm>
+#include <concepts>
+#include <cstdint>
+#include <cstdlib>
+#include <map>
+#include <optional>
+#include <ranges>
+#include <set>
+#include <string>
 #include <tuple>
 #include <type_traits>
+#include <unordered_set>
+#include <utility>
+#include <variant>
 #include <vector>
 
+#include <jvalidate/_macro.h>
 #include <jvalidate/compat/enumerate.h>
 #include <jvalidate/constraint/array_constraint.h>
 #include <jvalidate/constraint/general_constraint.h>
 #include <jvalidate/constraint/number_constraint.h>
 #include <jvalidate/constraint/object_constraint.h>
 #include <jvalidate/constraint/string_constraint.h>
-#include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/iostream.h>
 #include <jvalidate/detail/number.h>
 #include <jvalidate/detail/pointer.h>
 #include <jvalidate/detail/scoped_state.h>
+#include <jvalidate/detail/string.h>
 #include <jvalidate/detail/string_adapter.h>
+#include <jvalidate/detail/tribool.h>
 #include <jvalidate/format.h>
 #include <jvalidate/forward.h>
 #include <jvalidate/schema.h>
@@ -36,7 +49,8 @@
     }                                                                                              \
   } while (false)
 
-#define NOOP_UNLESS_TYPE(etype) RETURN_UNLESS(adapter::Type::etype == document.type(), Status::Noop)
+#define NOOP_UNLESS_TYPE(etype)                                                                    \
+  JVALIDATE_RETURN_UNLESS(adapter::Type::etype == document.type(), Status::Noop)
 
 #define BREAK_EARLY_IF_NO_RESULT_TREE()                                                            \
   do {                                                                                             \
@@ -248,13 +262,13 @@ private:
   Status visit(constraint::MaximumConstraint const & cons, Adapter auto const & document) const {
     switch (document.type()) {
     case adapter::Type::Integer:
-      if (int64_t value = document.as_integer(); not cons(value)) {
+      if (int64_t const value = document.as_integer(); not cons(value)) {
         return result(Status::Reject, value, cons.exclusive ? " >= " : " > ", cons.value);
       } else {
         return result(Status::Accept, value, cons.exclusive ? " < " : " <= ", cons.value);
       }
     case adapter::Type::Number:
-      if (double value = document.as_number(); not cons(value)) {
+      if (double const value = document.as_number(); not cons(value)) {
         return result(Status::Reject, value, cons.exclusive ? " >= " : " > ", cons.value);
       } else {
         return result(Status::Accept, value, cons.exclusive ? " < " : " <= ", cons.value);
@@ -267,13 +281,13 @@ private:
   Status visit(constraint::MinimumConstraint const & cons, Adapter auto const & document) const {
     switch (document.type()) {
     case adapter::Type::Integer:
-      if (int64_t value = document.as_integer(); not cons(value)) {
+      if (int64_t const value = document.as_integer(); not cons(value)) {
         return result(Status::Reject, value, cons.exclusive ? " <= " : " < ", cons.value);
       } else {
         return result(Status::Accept, value, cons.exclusive ? " > " : " >= ", cons.value);
       }
     case adapter::Type::Number:
-      if (double value = document.as_number(); not cons(value)) {
+      if (double const value = document.as_number(); not cons(value)) {
         return result(Status::Reject, value, cons.exclusive ? " <= " : " < ", cons.value);
       } else {
         return result(Status::Accept, value, cons.exclusive ? " > " : " >= ", cons.value);
@@ -285,33 +299,34 @@ private:
 
   Status visit(constraint::MultipleOfConstraint const & cons, Adapter auto const & document) const {
     adapter::Type const type = document.type();
-    RETURN_UNLESS(type == adapter::Type::Number || type == adapter::Type::Integer, Status::Noop);
+    JVALIDATE_RETURN_IF(type != adapter::Type::Number && type != adapter::Type::Integer,
+                        Status::Noop);
 
-    if (double value = document.as_number(); not cons(value)) {
-      return result(Status::Reject, value, " is not a multiple of ", cons.value);
-    } else {
+    double const value = document.as_number();
+    if (cons(value)) {
       return result(Status::Accept, value, " is a multiple of ", cons.value);
     }
+    return result(Status::Reject, value, " is not a multiple of ", cons.value);
   }
 
   Status visit(constraint::MaxLengthConstraint const & cons, Adapter auto const & document) const {
     NOOP_UNLESS_TYPE(String);
     std::string const str = document.as_string();
-    if (int64_t len = detail::length(str); len > cons.value) {
+    size_t const len = detail::length(str);
+    if (len > cons.value) {
       return result(Status::Reject, "string of length ", len, " is >", cons.value);
-    } else {
-      return result(Status::Accept, "string of length ", len, " is <=", cons.value);
     }
+    return result(Status::Accept, "string of length ", len, " is <=", cons.value);
   }
 
   Status visit(constraint::MinLengthConstraint const & cons, Adapter auto const & document) const {
     NOOP_UNLESS_TYPE(String);
     std::string const str = document.as_string();
-    if (int64_t len = detail::length(str); len < cons.value) {
+    size_t const len = detail::length(str);
+    if (len < cons.value) {
       return result(Status::Reject, "string of length ", len, " is <", cons.value);
-    } else {
-      return result(Status::Accept, "string of length ", len, " is >=", cons.value);
     }
+    return result(Status::Accept, "string of length ", len, " is >=", cons.value);
   }
 
   Status visit(constraint::PatternConstraint const & cons, Adapter auto const & document) const {
@@ -334,7 +349,7 @@ private:
       // Don't both validating formats if we're not in assertion mode
       // Assertion mode is specified either by using the appropriate "$vocab"
       // meta-schema or by requesting it in the ValidationConfig.
-      return true; // TODO: I think this can be made into Noop
+      return true; // TODO(samjaffe): I think this can be made into Noop
     }
 
     switch (format_(cons.format, cons.for_version, document.as_string())) {
@@ -389,20 +404,20 @@ private:
 
   Status visit(constraint::MaxItemsConstraint const & cons, Adapter auto const & document) const {
     NOOP_UNLESS_TYPE(Array);
-    if (size_t size = document.array_size(); size > cons.value) {
+    size_t const size = document.array_size();
+    if (size > cons.value) {
       return result(Status::Reject, "array of size ", size, " is >", cons.value);
-    } else {
-      return result(Status::Accept, "array of size ", size, " is <=", cons.value);
     }
+    return result(Status::Accept, "array of size ", size, " is <=", cons.value);
   }
 
   Status visit(constraint::MinItemsConstraint const & cons, Adapter auto const & document) const {
     NOOP_UNLESS_TYPE(Array);
-    if (size_t size = document.array_size(); size < cons.value) {
+    size_t const size = document.array_size();
+    if (size < cons.value) {
       return result(Status::Reject, "array of size ", size, " is <", cons.value);
-    } else {
-      return result(Status::Accept, "array of size ", size, " is >=", cons.value);
     }
+    return result(Status::Accept, "array of size ", size, " is >=", cons.value);
   }
 
   Status visit(constraint::TupleConstraint const & cons, Adapter auto const & document) const {
@@ -424,7 +439,7 @@ private:
   }
 
   template <Adapter A>
-  Status visit(constraint::UniqueItemsConstraint const & cons, A const & document) const {
+  Status visit(constraint::UniqueItemsConstraint const &, A const & document) const {
     NOOP_UNLESS_TYPE(Array);
 
     if constexpr (std::totally_ordered<A>) {
@@ -510,21 +525,21 @@ private:
   Status visit(constraint::MaxPropertiesConstraint const & cons,
                Adapter auto const & document) const {
     NOOP_UNLESS_TYPE(Object);
-    if (size_t size = document.object_size(); size > cons.value) {
+    size_t const size = document.object_size();
+    if (size > cons.value) {
       return result(Status::Reject, "object of size ", size, " is >", cons.value);
-    } else {
-      return result(Status::Accept, "object of size ", size, " is <=", cons.value);
     }
+    return result(Status::Accept, "object of size ", size, " is <=", cons.value);
   }
 
   Status visit(constraint::MinPropertiesConstraint const & cons,
                Adapter auto const & document) const {
     NOOP_UNLESS_TYPE(Object);
-    if (size_t size = document.object_size(); size < cons.value) {
+    size_t const size = document.object_size();
+    if (size < cons.value) {
       return result(Status::Reject, "object of size ", size, " is <", cons.value);
-    } else {
-      return result(Status::Accept, "object of size ", size, " is >=", cons.value);
     }
+    return result(Status::Accept, "object of size ", size, " is >=", cons.value);
   }
 
   Status visit(constraint::PatternPropertiesConstraint const & cons,
@@ -692,7 +707,7 @@ public:
     // constraints. This is enforced in the parsing of the schema, rather than
     // during validation {@see jvalidate::schema::Node::construct}.
     if (std::optional<schema::Node const *> ref = schema_->reference_schema()) {
-      // TODO: Investigate why this seems to produce .../$ref/$ref pointers
+      // TODO(samjaffe): Investigate why this seems to produce .../$ref/$ref pointers
       rval = validate_subschema(*ref, document, "$ref");
     }
 
@@ -704,7 +719,7 @@ public:
     for (auto const & [key, p_constraint] : schema_->constraints()) {
       BREAK_EARLY_IF_NO_RESULT_TREE();
       schema_path_ = current_schema / key;
-      rval &= std::visit([this, &document](auto & c) { return this->visit(c, document); },
+      rval &= std::visit([this, &document](auto & cons) { return this->visit(cons, document); },
                          *p_constraint);
     }
 
@@ -713,7 +728,7 @@ public:
     for (auto const & [key, p_constraint] : schema_->post_constraints()) {
       BREAK_EARLY_IF_NO_RESULT_TREE();
       schema_path_ = current_schema / key;
-      rval &= std::visit([this, &document](auto & c) { return this->visit(c, document); },
+      rval &= std::visit([this, &document](auto & cons) { return this->visit(cons, document); },
                          *p_constraint);
     }
 
@@ -736,9 +751,7 @@ public:
    * @return A ScopedState object that will restore the tracking mode once it
    * is destroyed.
    */
-  [[nodiscard]] detail::ScopedState invert_tracking() const {
-    return detail::ScopedState(tracking_, !tracking_);
-  }
+  [[nodiscard]] auto invert_tracking() const { return detail::ScopedState(tracking_, !tracking_); }
 
   /**
    * @brief Allow ExtensionVisitor to enable tracking of all results in child
@@ -747,7 +760,7 @@ public:
    * @return A ScopedState object that will restore the tracking mode once it
    * is destroyed.
    */
-  [[nodiscard]] detail::ScopedState track_everything() const {
+  [[nodiscard]] auto track_everything() const {
     return detail::ScopedState(tracking_, StoreResults::ForAnything);
   }
 
@@ -789,10 +802,9 @@ public:
                             Adapter auto const & document, K const &... keys) const {
     if (schema::Node const * const * ppschema = std::get_if<0>(&subschema)) {
       return validate_subschema(*ppschema, document, keys...);
-    } else {
-      return std::visit([this, &document](auto & c) { return this->visit(c, document); },
-                        *std::get<1>(subschema));
     }
+    return std::visit([this, &document](auto & cons) { return this->visit(cons, document); },
+                      *std::get<1>(subschema));
   }
 
   /**

+ 14 - 12
include/jvalidate/validator.h

@@ -1,9 +1,11 @@
 #pragma once
 
+#include <jvalidate/_config.h> // IWYU pragma: keep
+#include <jvalidate/_macro.h>
+#include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/on_block_exit.h>
 #include <jvalidate/forward.h>
 #include <jvalidate/regex.h>
-#include <jvalidate/status.h>
 #include <jvalidate/validation_config.h>
 #include <jvalidate/validation_visitor.h>
 
@@ -28,8 +30,8 @@ class Validator {
 private:
   schema::Node const & schema_;
   ValidationConfig cfg_;
-  ExtensionVisitor extension_;
-  RE regex_;
+  ExtensionVisitor extension_{};
+  RE regex_{};
   FormatValidator format_{RE::is_regex};
 
 public:
@@ -41,7 +43,7 @@ public:
    * @param cfg Any special (runtime) configuration rules being applied to the
    * validator.
    */
-  Validator(schema::Node const & schema, ValidationConfig const & cfg = {})
+  explicit Validator(schema::Node const & schema, ValidationConfig const & cfg = {})
       : schema_(schema), cfg_(cfg) {}
 
   /**
@@ -55,8 +57,8 @@ public:
    * @param cfg Any special (runtime) configuration rules being applied to the
    * validator.
    */
-  Validator(schema::Node const & schema, ExtensionVisitor extension,
-            ValidationConfig const & cfg = {})
+  explicit Validator(schema::Node const & schema, ExtensionVisitor extension,
+                     ValidationConfig const & cfg = {})
       : schema_(schema), cfg_(cfg), extension_(extension) {}
 
   /**
@@ -117,9 +119,9 @@ public:
   bool validate(A const & json, ValidationResult * result = nullptr) {
     EXPECT_M(not cfg_.construct_default_values,
              "Cannot perform mutations on an immutable JSON Adapter");
-    detail::OnBlockExit _ = [&result, this]() { post_process(result); };
-    return static_cast<bool>(
-        ValidationVisitor(schema_, json, cfg_, regex_, format_, extension_, result).validate(json));
+    detail::OnBlockExit const _ = [&result, this]() { post_process(result); };
+    ValidationVisitor visitor(schema_, json, cfg_, regex_, format_, extension_, result);
+    return static_cast<bool>(visitor.validate(json));
   }
 
   /**
@@ -137,9 +139,9 @@ public:
    * schema to provide a record of all of the failures.
    */
   template <MutableAdapter A> bool validate(A const & json, ValidationResult * result = nullptr) {
-    detail::OnBlockExit _ = [&result, this]() { post_process(result); };
-    return static_cast<bool>(
-        ValidationVisitor(schema_, json, cfg_, regex_, format_, extension_, result).validate(json));
+    detail::OnBlockExit const _ = [&result, this]() { post_process(result); };
+    ValidationVisitor visitor(schema_, json, cfg_, regex_, format_, extension_, result);
+    return static_cast<bool>(visitor.validate(json));
   }
 
   /**

+ 3 - 1
include/jvalidate/vocabulary.h

@@ -1,4 +1,5 @@
 #pragma once
+// NOLINTBEGIN(readability-identifier-naming, google-explicit-constructor)
 
 #include <functional>
 #include <memory>
@@ -100,7 +101,7 @@ template <Adapter A> struct Metadata {
   Metadata(decltype(Removed)) : valid(false) {}
   Metadata(decltype(Literal)) {}
   Metadata(decltype(KeywordMap)) : type(KeywordType::KeywordMap) {}
-  Metadata(DependentKeyword dep) : type(KeywordType::Keyword) {}
+  Metadata(DependentKeyword) : type(KeywordType::Keyword) {}
 
   template <typename F> Metadata(F make) : make(make) {}
   template <typename F>
@@ -120,3 +121,4 @@ template <Adapter A> struct Metadata {
   bool is_post_constraint = false;
 };
 }
+// NOLINTEND(readability-identifier-naming, google-explicit-constructor)

+ 17 - 14
src/validate.cxx

@@ -1,12 +1,12 @@
 #include <cstdio>
 #include <cstdlib>
-#include <filesystem>
 #include <iostream>
+#include <map>
 #include <sstream>
 #include <stdexcept>
+#include <string>
 #include <string_view>
-
-#include <curl/curl.h>
+#include <utility>
 
 #include <jvalidate/adapter.h>
 #include <jvalidate/adapters/jsoncpp.h>
@@ -14,8 +14,6 @@
 #include <jvalidate/enum.h>
 #include <jvalidate/forward.h>
 #include <jvalidate/schema.h>
-#include <jvalidate/status.h>
-#include <jvalidate/uri.h>
 #include <jvalidate/validator.h>
 
 #include <json/value.h>
@@ -24,10 +22,11 @@
 using jvalidate::adapter::load_file;
 using jvalidate::adapter::load_stream;
 
+namespace {
 struct ProgramArgs {
-  ProgramArgs(std::string_view program, std::vector<std::string_view> args) {
+  ProgramArgs(std::string_view program, std::vector<std::string_view> args) { // NOLINT
     size_t nargs = 0;
-    for (size_t i = 0; i < args.size(); ++i) {
+    for (size_t i = 0; i < args.size(); ++i) { // NOLINT
       if (args[i] == "--verbose" || args[i] == "-v") {
         verbose = true;
       } else if (args[i] == "--explain") {
@@ -40,6 +39,8 @@ struct ProgramArgs {
         case 1:
           object = args[i];
           break;
+        default:
+          break;
         }
       }
     }
@@ -55,26 +56,28 @@ struct ProgramArgs {
   std::string_view schema;
   std::string_view object;
 };
+}
 
-int main(int argc, char const * const * argv) {
+int main(int argc, char const * const * argv) { // NOLINT
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
   ProgramArgs const args{argv[0], {argv + 1, argv + argc}};
 
   Json::Value jschema;
   Json::Value jobject;
   if (std::string error; !load_file(args.schema, jschema, error)) {
-    std::cerr << "Error loading schema: " << error << std::endl;
+    std::cerr << "Error loading schema: " << error << "\n";
     return EXIT_FAILURE;
   }
   if (std::string error; !load_file(args.object, jobject, error)) {
-    std::cerr << "Error loading schema: " << error << std::endl;
+    std::cerr << "Error loading schema: " << error << "\n";
     return EXIT_FAILURE;
   }
 
   using enum jvalidate::schema::Version;
-  jvalidate::Schema schema(jschema, Draft2020_12, &jvalidate::curl_get<Json::Value>);
+  jvalidate::Schema const schema(jschema, Draft2020_12, &jvalidate::curl_get<Json::Value>);
 
   jvalidate::ValidationResult result;
-  bool compact_error = !args.verbose && !args.format_as_explaination;
+  bool const compact_error = !args.verbose && !args.format_as_explaination;
   if (jvalidate::Validator(schema, {.only_return_results_with_error = compact_error})
           .validate(jobject, &result)) {
     std::cout << result << "\n";
@@ -97,7 +100,7 @@ int main(int argc, char const * const * argv) {
   std::map<std::string, Json::Value> because;
   for (Json::Value const & elem : json["details"]) {
     std::string const & path = elem["evaluationPath"].asString();
-    if (size_t pos = path.find("/if"); pos != std::string::npos && elem.isMember("errors")) {
+    if (size_t const pos = path.find("/if"); pos != std::string::npos && elem.isMember("errors")) {
       because.emplace(path.substr(0, pos), elem);
     }
   }
@@ -117,7 +120,7 @@ int main(int argc, char const * const * argv) {
 
     out.append(std::move(elem));
   }
-  std::cout << out << std::endl;
+  std::cout << out << "\n";
 
   return EXIT_FAILURE;
 }

+ 10 - 0
tests/.clang-tidy

@@ -0,0 +1,10 @@
+---
+InheritParentConfig: true
+Checks: >
+  -cppcoreguidelines-avoid-magic-numbers,
+  -google-build-using-namespace,
+  -misc-const-correctness,
+  -misc-use-internal-linkage,
+  -readability-convert-member-functions-to-static,
+  -readability-identifier-naming,
+...

+ 2 - 2
tests/adapter_test.cxx

@@ -1,11 +1,11 @@
+#include <optional>
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include <json/value.h>
 
 #include <jvalidate/adapters/jsoncpp.h>
-#include <jvalidate/detail/array_iterator.h>
-#include <jvalidate/detail/object_iterator.h>
 
 using namespace jvalidate::adapter;
 

+ 9 - 7
tests/annotation_test.cxx

@@ -1,23 +1,24 @@
+#include <sstream>
+#include <vector>
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <json/value.h>
 
-#include <jvalidate/adapter.h>
-#include <jvalidate/adapters/jsoncpp.h>
+#include <jvalidate/adapters/jsoncpp.h> // IWYU pragma: keep
 #include <jvalidate/enum.h>
 #include <jvalidate/schema.h>
-#include <jvalidate/status.h>
-#include <jvalidate/uri.h>
 #include <jvalidate/validation_result.h>
 #include <jvalidate/validator.h>
-#include <sstream>
 
 #include "matchers.h"
 
 using enum jvalidate::schema::Version;
 using testing::Not;
 
-static auto validate(Json::Value const & schema_doc, Json::Value const & instance_doc,
-                     jvalidate::schema::Version version = Draft2020_12) {
+namespace {
+auto validate(Json::Value const & schema_doc, Json::Value const & instance_doc,
+              jvalidate::schema::Version version = Draft2020_12) {
   jvalidate::Schema const schema(schema_doc, version);
 
   jvalidate::ValidationResult result;
@@ -25,6 +26,7 @@ static auto validate(Json::Value const & schema_doc, Json::Value const & instanc
 
   return result;
 }
+}
 
 TEST(AnnotationTest, AttachesFormattingAnnotation) {
   auto const schema = R"({

+ 6 - 4
tests/custom_filter.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <algorithm>
+#include <cstddef>
 #include <regex>
 #include <string>
 #include <vector>
@@ -12,10 +13,10 @@ private:
 public:
   explicit Glob(std::string glob) {
     for (size_t i = glob.find('.'); i != std::string::npos; i = glob.find('.', i + 2)) {
-      glob.insert(glob.begin() + i, '\\');
+      glob.insert(glob.begin() + static_cast<ptrdiff_t>(i), '\\');
     }
     for (size_t i = glob.find('*'); i != std::string::npos; i = glob.find('*', i + 2)) {
-      glob.insert(glob.begin() + i, '.');
+      glob.insert(glob.begin() + static_cast<ptrdiff_t>(i), '.');
     }
     re_ = std::regex(glob);
   }
@@ -28,7 +29,8 @@ struct RecursiveTestFilter {
   std::vector<Glob> blacklist;
 
   bool accepts(std::string const & str) const {
-    return std::count(blacklist.begin(), blacklist.end(), str) == 0 and
-           (whitelist.empty() or std::count(whitelist.begin(), whitelist.end(), str) > 0);
+    auto in = [&str](auto & glob) { return glob == str; };
+    return std::ranges::count_if(blacklist, in) == 0 and
+           (whitelist.empty() or std::ranges::count_if(whitelist, in) > 0);
   }
 };

+ 4 - 0
tests/detail_test.cxx

@@ -1,8 +1,10 @@
+#include <cstdlib>
 #include <stdexcept>
 #include <type_traits>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <json/value.h>
 
 #include <jvalidate/adapters/jsoncpp.h>
 #include <jvalidate/detail/anchor.h>
@@ -13,6 +15,8 @@
 #include <jvalidate/detail/string.h>
 #include <jvalidate/detail/string_adapter.h>
 #include <jvalidate/enum.h>
+#include <jvalidate/status.h>
+#include <jvalidate/uri.h>
 
 #include "matchers.h"
 

+ 1 - 0
tests/enum_test.cxx

@@ -1,4 +1,5 @@
 #include <compare>
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 

+ 8 - 4
tests/extension_test.cxx

@@ -1,10 +1,12 @@
 #include <jvalidate/extension.h>
 
+#include <string_view>
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <json/value.h>
 
-#include <jvalidate/adapters/jsoncpp.h>
+#include <jvalidate/adapters/jsoncpp.h> // IWYU pragma: keep
 #include <jvalidate/constraint/extension_constraint.h>
 #include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/relative_pointer.h>
@@ -20,7 +22,7 @@ using jvalidate::constraint::ExtensionConstraint;
 using testing::Not;
 
 struct IsKeyOfConstraint : jvalidate::extension::ConstraintBase<IsKeyOfConstraint> {
-  IsKeyOfConstraint(std::string_view ptr) : ptr(ptr) {
+  explicit IsKeyOfConstraint(std::string_view ptr) : ptr(ptr) {
     EXPECT_M(ptr.find('/') != std::string_view::npos,
              "IsKeyOfConstraint requires a value-relative-pointer, not a key-relative-pointer");
   }
@@ -42,8 +44,9 @@ public:
   }
 };
 
-static auto validate(Json::Value const & schema_doc, Json::Value const & instance_doc,
-                     jvalidate::schema::Version version = Draft2020_12) {
+namespace {
+auto validate(Json::Value const & schema_doc, Json::Value const & instance_doc,
+              jvalidate::schema::Version version = Draft2020_12) {
   using A = jvalidate::adapter::AdapterFor<Json::Value const>;
   jvalidate::ConstraintFactory<A> factory{{"is_key_of", [](auto const & context) {
                                              return ExtensionConstraint::make<IsKeyOfConstraint>(
@@ -56,6 +59,7 @@ static auto validate(Json::Value const & schema_doc, Json::Value const & instanc
 
   return result;
 }
+}
 
 TEST(ExtensionConstraintTest, CanReportSuccess) {
   auto schema = R"({

+ 10 - 9
tests/json_schema_test_suite.h

@@ -29,38 +29,39 @@ inline std::string to_string(jvalidate::schema::Version version) {
 }
 
 inline auto SchemaTests(jvalidate::schema::Version version) {
-  struct fs_iterator : std::filesystem::recursive_directory_iterator {
+  struct FsIterator : std::filesystem::recursive_directory_iterator {
     using super = std::filesystem::recursive_directory_iterator;
     using value_type = SchemaParams;
     using reference = SchemaParams;
 
-    fs_iterator() = default;
-    explicit fs_iterator(jvalidate::schema::Version version)
+    FsIterator() = default;
+    explicit FsIterator(jvalidate::schema::Version version)
         : super(JSONSchemaTestSuiteDir() / "tests" / to_string(version)), version(version) {}
 
     SchemaParams operator*() const { return std::make_tuple(version, super::operator*().path()); }
 
-    fs_iterator operator++() {
-      do {
+    FsIterator operator++() {
+      super::operator++();
+      while (*this != FsIterator() && super::operator*().is_directory()) {
         super::operator++();
-      } while (*this != fs_iterator() && super::operator*().is_directory());
+      }
       return *this;
     }
 
     jvalidate::schema::Version version;
   };
 
-  return testing::ValuesIn(fs_iterator(version), fs_iterator());
+  return testing::ValuesIn(FsIterator(version), FsIterator());
 }
 
-static auto SchemaTestName = [](auto const & info) {
+static auto const SchemaTestName = [](auto const & info) {
   auto [version, file] = info.param;
   auto base = JSONSchemaTestSuiteDir() / "tests" / to_string(version);
 
   std::string name = std::filesystem::relative(file, base);
   name = name.substr(0, name.rfind('.'));
   std::transform(name.begin(), name.end(), name.begin(),
-                 [](char c) { return std::isalnum(c) ? c : '_'; });
+                 [](char chr) { return std::isalnum(chr) ? chr : '_'; });
   return name;
 };
 

+ 3 - 1
tests/jsoncpp_adapter_test.cxx

@@ -3,10 +3,12 @@
 
 #include <json/value.h>
 
-#include <jvalidate/adapter.h>
 #include <jvalidate/adapters/jsoncpp.h>
 #include <jvalidate/detail/array_iterator.h>
 #include <jvalidate/detail/object_iterator.h>
+#include <jvalidate/detail/simple_adapter.h>
+#include <jvalidate/enum.h>
+#include <jvalidate/forward.h>
 
 using namespace jvalidate::adapter;
 using testing::IsEmpty;

+ 2 - 2
tests/matchers.h

@@ -1,4 +1,3 @@
-#include "gtest/internal/gtest-internal.h"
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
@@ -12,13 +11,14 @@ inline auto operator""_jptr(char const * data, size_t len) {
   return jvalidate::detail::Pointer(std::string_view{data, len});
 }
 
-inline Json::Value const operator""_json(char const * data, size_t len) {
+inline Json::Value operator""_json(char const * data, size_t len) {
   Json::Value value;
 
   Json::CharReaderBuilder builder;
   std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
 
   std::string error;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
   if (not reader->parse(data, data + len, &value, &error)) {
     throw std::runtime_error(error);
   }

+ 0 - 1
tests/regex_test.cxx

@@ -1,6 +1,5 @@
 #include "jvalidate/regex.h"
 
-#include "gtest/gtest.h"
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 

+ 17 - 12
tests/selfvalidate_test.cxx

@@ -1,23 +1,25 @@
+#include <array>
+#include <cstddef>
 #include <cstdio>
 #include <cstdlib>
+#include <exception>
 #include <filesystem>
 #include <iostream>
+#include <string>
+#include <string_view>
+#include <vector>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <json/value.h>
+#include <json/writer.h> // IWYU pragma: keep
 
 #include <jvalidate/adapter.h>
-#include <jvalidate/adapters/jsoncpp.h>
+#include <jvalidate/adapters/jsoncpp.h> // IWYU pragma: keep
 #include <jvalidate/compat/curl.h>
 #include <jvalidate/enum.h>
 #include <jvalidate/schema.h>
-#include <jvalidate/status.h>
 #include <jvalidate/uri.h>
-#include <jvalidate/validator.h>
-
-#include <json/reader.h>
-#include <json/value.h>
-#include <json/writer.h>
 
 #include "./custom_filter.h"
 #include "./json_schema_test_suite.h"
@@ -47,7 +49,7 @@ protected:
   bool skip_suite(std::string const & desc) const { return not s_suite_filter.accepts(desc); }
   bool skip_case(std::string const & desc) const { return not s_case_filter.accepts(desc); }
 
-  static void SetUpTestCase() {
+  static void SetUpTestSuite() {
     auto tokenize = [](auto & into, std::string const & in) {
       std::vector<std::string> tokens;
       testing::internal::SplitString(in, ':', &tokens);
@@ -58,7 +60,7 @@ protected:
         }
         tokens[i - 1] += ":";
         tokens[i - 1] += tokens[i];
-        tokens.erase(tokens.begin() + i);
+        tokens.erase(tokens.begin() + static_cast<ptrdiff_t>(i));
       }
       for (auto & tok : tokens) {
         into.emplace_back(tok);
@@ -66,7 +68,7 @@ protected:
     };
 
     for (std::string_view str : testing::internal::GetArgvs()) {
-      RecursiveTestFilter * ptr;
+      RecursiveTestFilter * ptr = nullptr;
       if (str.starts_with("--json_suite_filter=")) {
         str.remove_prefix(20);
         ptr = &s_suite_filter;
@@ -79,6 +81,7 @@ protected:
 
       size_t const pos_end = str[0] == '-' ? 0 : str.find(":-");
       size_t const neg_start =
+          // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator)
           pos_end == 0 ? 1 : (pos_end == std::string::npos ? pos_end : pos_end + 2);
 
       if (pos_end > 0 && not str.empty()) {
@@ -106,7 +109,7 @@ TEST_P(JsonSchemaTest, TestSuite) {
     if (skip_suite(suite["description"].asString())) {
       continue;
     }
-    std::cout << "\033[0;32m[ SUITE    ] \033[0;0m" << suite["description"].asString() << std::endl;
+    std::cout << "\033[0;32m[ SUITE    ] \033[0;0m" << suite["description"].asString() << "\n";
     try {
       jvalidate::Schema schema(suite["schema"], version, &load_external_for_test);
       for (auto const & test : suite["tests"]) {
@@ -115,7 +118,7 @@ TEST_P(JsonSchemaTest, TestSuite) {
         }
         try {
           std::cout << "\033[0;32m[ CASE     ] \033[0;0m    " << test["description"].asString()
-                    << std::endl;
+                    << "\n";
           EXPECT_THAT(test["data"], ValidatesAgainst(schema, test, is_format)) << suite["schema"];
         } catch (std::exception const & ex) { ADD_FAILURE() << ex.what() << "\n" << test; }
       }
@@ -134,6 +137,7 @@ INSTANTIATE_TEST_SUITE_P(Draft2019_09, JsonSchemaTest, SchemaTests(Version::Draf
 INSTANTIATE_TEST_SUITE_P(Draft2020_12, JsonSchemaTest, SchemaTests(Version::Draft2020_12),
                          SchemaTestName);
 
+// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
 int main(int argc, char ** argv) {
   using std::string_literals::operator""s;
   if (argc != 4) {
@@ -147,3 +151,4 @@ int main(int argc, char ** argv) {
   testing::InitGoogleMock(&argc, argv);
   return RUN_ALL_TESTS();
 }
+// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)

+ 16 - 8
tests/validation_visitor_test.cxx

@@ -1,18 +1,26 @@
 #include <jvalidate/validation_visitor.h>
 
+#include <utility>
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <json/value.h>
 
 #include <jvalidate/adapters/jsoncpp.h>
+#include <jvalidate/constraint/extension_constraint.h>
 #include <jvalidate/constraint/general_constraint.h>
+#include <jvalidate/constraint/string_constraint.h>
 #include <jvalidate/detail/pointer.h>
 #include <jvalidate/enum.h>
+#include <jvalidate/forward.h>
 #include <jvalidate/regex.h>
 #include <jvalidate/schema.h>
+#include <jvalidate/status.h>
+#include <jvalidate/validation_config.h>
+#include <jvalidate/validation_result.h>
 #include <jvalidate/validator.h>
 
-#include "matchers.h"
+#include "./matchers.h"
 
 using enum jvalidate::schema::Version;
 using jvalidate::Status;
@@ -24,20 +32,18 @@ using testing::Eq;
 namespace jvalidate {
 class ValidationVisitorTest : public testing::Test {
 protected:
-  template <typename JSON = Json::Value const>
-  auto visit(jvalidate::detail::Pointer ptr, auto const & cons, JSON & json,
+  auto visit(jvalidate::detail::Pointer ptr, auto const & cons, Json::Value const & json,
              jvalidate::ValidationResult * result = nullptr, bool annotate_everything = false) {
     JsonCppAdapter const adapter(json);
     ValidationVisitor visitor(node_, adapter, cfg_, regex_, format_, extension_, result);
     if (annotate_everything) {
       visitor.tracking_ = StoreResults::ForAnything;
     }
-    visitor.schema_path_ = ptr;
+    visitor.schema_path_ = std::move(ptr);
     return visitor.visit(cons, adapter);
   }
 
-  template <typename JSON = Json::Value const>
-  auto visit(jvalidate::Not<jvalidate::detail::Pointer> auto const & cons, JSON & json,
+  auto visit(jvalidate::Not<jvalidate::detail::Pointer> auto const & cons, Json::Value const & json,
              jvalidate::ValidationResult * result = nullptr, bool annotate_everything = false) {
     return visit({}, cons, json, result, annotate_everything);
   }
@@ -122,7 +128,8 @@ TEST_F(ValidationVisitorTest, EnumConstraintAnnotatesMatchingIndex) {
 }
 
 TEST_F(ValidationVisitorTest, UnknownFormatIsAccept) {
-  constraint::FormatConstraint cons{"bogus", Draft2020_12, true};
+  constraint::FormatConstraint cons{
+      .format = "bogus", .for_version = Draft2020_12, .is_assertion = true};
   config({.validate_format = true});
 
   ValidationResult result;
@@ -130,7 +137,8 @@ TEST_F(ValidationVisitorTest, UnknownFormatIsAccept) {
 }
 
 TEST_F(ValidationVisitorTest, UnimplementedFormatIsError) {
-  constraint::FormatConstraint cons{"bogus", Draft2020_12, true};
+  constraint::FormatConstraint cons{
+      .format = "bogus", .for_version = Draft2020_12, .is_assertion = true};
   config({.validate_format = true});
   format({{"bogus", nullptr}});