From dbb04f9ba2fe2fab938a37967a4b892b0a704b4d Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 13 Feb 2025 10:02:01 -0400 Subject: [PATCH 1/2] Implement `PointerTemplate::conditional_of` to loosely compare templates Signed-off-by: Juan Cruz Viotti --- .../sourcemeta/core/jsonpointer_template.h | 54 +++++- test/jsonpointer/jsonpointer_template_test.cc | 155 ++++++++++++++++++ 2 files changed, 204 insertions(+), 5 deletions(-) diff --git a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h index 07f63d51b..6ecfa50ec 100644 --- a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h +++ b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h @@ -4,11 +4,12 @@ #include #include -#include // std::copy -#include // assert -#include // std::back_inserter -#include // std::variant -#include // std::vector +#include // std::copy, std::equal +#include // assert +#include // std::reference_wrapper +#include // std::back_inserter +#include // std::variant, std::holds_alternative +#include // std::vector namespace sourcemeta::core { @@ -203,6 +204,49 @@ template class GenericPointerTemplate { return this->data.empty(); } + /// Check if a JSON Pointer template is equal to another JSON Pointer template + /// when not taking into account condition tokens. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::PointerTemplate left{ + /// sourcemeta::core::PointerTemplate::Condition{}, + /// sourcemeta::core::Pointer::Token{"foo"}}; + /// const sourcemeta::core::PointerTemplate right{ + /// sourcemeta::core::Pointer::Token{"foo"}}; + /// + /// assert(left.conditional_of(right)); + /// assert(right.conditional_of(left)); + /// ``` + [[nodiscard]] auto + conditional_of(const GenericPointerTemplate &other) const noexcept + -> bool { + std::vector> + this_filter; + std::vector> + that_filter; + + for (const auto &token : this->data) { + if (!std::holds_alternative(token)) { + this_filter.emplace_back(token); + } + } + + for (const auto &token : other.data) { + if (!std::holds_alternative(token)) { + that_filter.emplace_back(token); + } + } + + return std::equal(this_filter.cbegin(), this_filter.cend(), + that_filter.cbegin(), that_filter.cend(), + [](const auto &left, const auto &right) { + return left.get() == right.get(); + }); + } + /// Compare JSON Pointer template instances auto operator==(const GenericPointerTemplate &other) const noexcept -> bool { diff --git a/test/jsonpointer/jsonpointer_template_test.cc b/test/jsonpointer/jsonpointer_template_test.cc index ad3f82cff..0843102b6 100644 --- a/test/jsonpointer/jsonpointer_template_test.cc +++ b/test/jsonpointer/jsonpointer_template_test.cc @@ -310,3 +310,158 @@ TEST(JSONPointer_template, empty_false) { const sourcemeta::core::PointerTemplate pointer{base}; EXPECT_FALSE(pointer.empty()); } + +TEST(JSONPointer_template, conditional_of_empty) { + const sourcemeta::core::PointerTemplate left; + const sourcemeta::core::PointerTemplate right; + EXPECT_TRUE(left.conditional_of(right)); + EXPECT_TRUE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_equal_no_conditional_one) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::Pointer::Token{"foo"}}; + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::Pointer::Token{"foo"}}; + EXPECT_TRUE(left.conditional_of(right)); + EXPECT_TRUE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_equal_no_conditional_many) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::Pointer::Token{"foo"}, + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::Pointer::Token{"bar"}}; + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::Pointer::Token{"foo"}, + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::Pointer::Token{"bar"}}; + + EXPECT_TRUE(left.conditional_of(right)); + EXPECT_TRUE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_not_equal_no_conditional_one) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::Pointer::Token{"foo"}}; + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::Pointer::Token{"fo"}}; + EXPECT_FALSE(left.conditional_of(right)); + EXPECT_FALSE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_not_equal_no_conditional_many) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::Pointer::Token{"foo"}, + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::Pointer::Token{"bar"}}; + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::Pointer::Token{"foo"}, + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::Pointer::Token{"ba"}}; + + EXPECT_FALSE(left.conditional_of(right)); + EXPECT_FALSE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_equal_conditional_one) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::PointerTemplate::Condition{}}; + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::PointerTemplate::Condition{}}; + EXPECT_TRUE(left.conditional_of(right)); + EXPECT_TRUE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_equal_conditional_many) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}}; + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}}; + + EXPECT_TRUE(left.conditional_of(right)); + EXPECT_TRUE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_equal_conditional_different) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}}; + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}}; + + EXPECT_TRUE(left.conditional_of(right)); + EXPECT_TRUE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_equal_mix_1) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Wildcard{}, + sourcemeta::core::PointerTemplate::Negation{}, + sourcemeta::core::Pointer::Token{"foo"}, + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}}; + + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::PointerTemplate::Wildcard{}, + sourcemeta::core::PointerTemplate::Negation{}, + sourcemeta::core::Pointer::Token{"foo"}, + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::PointerTemplate::Condition{}}; + + EXPECT_TRUE(left.conditional_of(right)); + EXPECT_TRUE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_equal_mix_2) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Wildcard{}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Negation{}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}}; + + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Wildcard{}, + sourcemeta::core::PointerTemplate::Negation{}, + sourcemeta::core::PointerTemplate::Condition{}}; + + EXPECT_TRUE(left.conditional_of(right)); + EXPECT_TRUE(right.conditional_of(left)); +} + +TEST(JSONPointer_template, conditional_of_not_equal_mix_1) { + const sourcemeta::core::PointerTemplate left{ + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Wildcard{}, + sourcemeta::core::PointerTemplate::Negation{}, + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::Pointer::Token{"foo"}, + sourcemeta::core::PointerTemplate::Condition{}, + sourcemeta::core::PointerTemplate::Condition{}}; + + const sourcemeta::core::PointerTemplate right{ + sourcemeta::core::PointerTemplate::Wildcard{}, + sourcemeta::core::PointerTemplate::Negation{}, + sourcemeta::core::Pointer::Token{"foo"}, + sourcemeta::core::Pointer::Token{0}, + sourcemeta::core::PointerTemplate::Condition{}}; + + EXPECT_FALSE(left.conditional_of(right)); + EXPECT_FALSE(right.conditional_of(left)); +} From 5e27f263a551ab0e949579c0cd1cd55dd6f276ee Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 13 Feb 2025 10:26:12 -0400 Subject: [PATCH 2/2] REFACTOR Signed-off-by: Juan Cruz Viotti --- .../sourcemeta/core/jsonpointer_template.h | 50 +++++++++++-------- src/core/jsonschema/frame.cc | 6 +++ 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h index 6ecfa50ec..0ac561ab0 100644 --- a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h +++ b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h @@ -4,12 +4,11 @@ #include #include -#include // std::copy, std::equal -#include // assert -#include // std::reference_wrapper -#include // std::back_inserter -#include // std::variant, std::holds_alternative -#include // std::vector +#include // std::copy +#include // assert +#include // std::back_inserter +#include // std::variant, std::holds_alternative +#include // std::vector namespace sourcemeta::core { @@ -223,28 +222,35 @@ template class GenericPointerTemplate { [[nodiscard]] auto conditional_of(const GenericPointerTemplate &other) const noexcept -> bool { - std::vector> - this_filter; - std::vector> - that_filter; + auto iterator_this = this->data.cbegin(); + auto iterator_that = other.data.cbegin(); - for (const auto &token : this->data) { - if (!std::holds_alternative(token)) { - this_filter.emplace_back(token); + while (iterator_this != this->data.cend() && + iterator_that != other.data.cend()) { + while (iterator_this != this->data.cend() && + std::holds_alternative(*iterator_this)) { + iterator_this += 1; + } + + while (iterator_that != other.data.cend() && + std::holds_alternative(*iterator_that)) { + iterator_that += 1; } - } - for (const auto &token : other.data) { - if (!std::holds_alternative(token)) { - that_filter.emplace_back(token); + if (iterator_this == this->data.cend() || + iterator_that == other.data.cend()) { + return iterator_this == this->data.cend() && + iterator_that == other.data.cend(); + } else if (*iterator_this != *iterator_that) { + return false; + } else { + iterator_this += 1; + iterator_that += 1; } } - return std::equal(this_filter.cbegin(), this_filter.cend(), - that_filter.cbegin(), that_filter.cend(), - [](const auto &left, const auto &right) { - return left.get() == right.get(); - }); + return iterator_this == this->data.cend() && + iterator_that == other.data.cend(); } /// Compare JSON Pointer template instances diff --git a/src/core/jsonschema/frame.cc b/src/core/jsonschema/frame.cc index 06a60d165..8ded4cf2d 100644 --- a/src/core/jsonschema/frame.cc +++ b/src/core/jsonschema/frame.cc @@ -1107,6 +1107,12 @@ auto find_adjacent_dependencies( namespace sourcemeta::core { +// TODO: Refactor this entire function using `SchemaFrame`'s new `Instances` +// mode. We can loop over every subschema that defines `unevaluatedProperties` +// or `unevaluatedItems`, find all other subschemas with the same unresolved +// instance location (static dependency) or conditional equivalent unresolved +// instance location (dynamic dependency) and see if those ones define any of +// the dependent keywords. auto unevaluated(const JSON &schema, const SchemaFrame &frame, const SchemaWalker &walker, const SchemaResolver &resolver) -> SchemaUnevaluatedEntries {