diff --git a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h index 07f63d51b..0ac561ab0 100644 --- a/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h +++ b/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h @@ -7,7 +7,7 @@ #include // std::copy #include // assert #include // std::back_inserter -#include // std::variant +#include // std::variant, std::holds_alternative #include // std::vector namespace sourcemeta::core { @@ -203,6 +203,56 @@ 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 { + auto iterator_this = this->data.cbegin(); + auto iterator_that = other.data.cbegin(); + + 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; + } + + 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 iterator_this == this->data.cend() && + iterator_that == other.data.cend(); + } + /// Compare JSON Pointer template instances auto operator==(const GenericPointerTemplate &other) const noexcept -> bool { 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 { 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)); +}