Skip to content

Commit

Permalink
[WIP] Analyse adjacent schemas for unevaluated(Properties|Items)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
  • Loading branch information
jviotti committed Dec 24, 2024
1 parent 4d29401 commit 0dc5725
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 24 deletions.
99 changes: 82 additions & 17 deletions src/jsonschema/unevaluated.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,94 @@
#include <cassert> // assert
#include <utility> // std::move

#include <iostream> // TODO DEBUG

namespace {
using namespace sourcemeta::jsontoolkit;

auto navigate_frame(const FrameLocations &frame,
const FrameLocationsEntry &entry,
const Pointer &relative_pointer)
-> const FrameLocationsEntry & {
const auto new_uri{
to_uri(entry.relative_pointer.concat(relative_pointer), entry.base)
.recompose()};
assert(frame.contains({ReferenceType::Static, new_uri}));
return frame.at({ReferenceType::Static, new_uri});
}

// TODO: Extract all of this into a public utility to traverse
// adjacent subschemas
auto find_adjacent_dependencies(const JSON &schema, const JSON::String &current,
const FrameLocations &frame,
const FrameLocationsEntry &entry,
const SchemaWalker &walker,
const SchemaResolver &resolver,
const std::set<JSON::String> &keywords,
std::set<Pointer> &dependencies) -> bool {
const auto &subschema{get(schema, entry.relative_pointer)};
if (!subschema.is_object()) {
return true;
}

auto find_adjacent_dependencies(
const sourcemeta::jsontoolkit::JSON &subschema,
const sourcemeta::jsontoolkit::FrameLocationsEntry &entry,
const std::set<sourcemeta::jsontoolkit::JSON::String> &keywords,
std::set<sourcemeta::jsontoolkit::Pointer> &dependencies) -> bool {
for (const auto &keyword : keywords) {
// TODO: Consider adjacent cases too
if (subschema.defines(keyword)) {
dependencies.emplace(entry.relative_pointer.concat({keyword}));
const auto subschema_vocabularies{
vocabularies(subschema, resolver, entry.dialect)};

std::cerr << "TRAVERSING: ";
stringify(subschema, std::cerr);
std::cerr << "\n";

bool result{true};
for (const auto &property : subschema.as_object()) {
if (keywords.contains(property.first)) {
dependencies.emplace(entry.relative_pointer.concat({property.first}));
continue;
} else if (property.first == current) {
continue;
}

if (property.first == "allOf" && property.second.is_array()) {
for (std::size_t index = 0; index < property.second.size(); index++) {
if (!find_adjacent_dependencies(
schema, current, frame,
navigate_frame(frame, entry, {property.first, index}), walker,
resolver, keywords, dependencies)) {
result = false;
}
}

continue;
}

std::cerr << "XXX UNKNOWN: " << property.first << "\n";
const auto strategy{
walker(property.first, subschema_vocabularies).strategy};
switch (strategy) {
case SchemaWalkerStrategy::Unknown:
continue;
case SchemaWalkerStrategy::Assertion:
continue;
case SchemaWalkerStrategy::Annotation:
continue;
case SchemaWalkerStrategy::Reference:
continue;
case SchemaWalkerStrategy::Other:
continue;
default:
result = false;
break;
}
}

return false;
return result;
}

} // namespace

namespace sourcemeta::jsontoolkit {

auto unevaluated(const JSON &schema, const FrameLocations &frame,
const FrameReferences &, const SchemaWalker &,
const FrameReferences &, const SchemaWalker &walker,
const SchemaResolver &resolver) -> UnevaluatedEntries {
UnevaluatedEntries result;

Expand Down Expand Up @@ -57,7 +122,7 @@ auto unevaluated(const JSON &schema, const FrameLocations &frame,
"https://json-schema.org/draft/2020-12/vocab/applicator")) &&
pair.first == "unevaluatedProperties") {
const auto fully_resolved{find_adjacent_dependencies(
subschema, entry.second,
schema, pair.first, frame, entry.second, walker, resolver,
{"properties", "patternProperties", "additionalProperties"},
dependencies)};
result.emplace(keyword_uri, UnevaluatedEntry{std::move(dependencies),
Expand All @@ -69,16 +134,16 @@ auto unevaluated(const JSON &schema, const FrameLocations &frame,
"https://json-schema.org/draft/2020-12/vocab/applicator")) &&
pair.first == "unevaluatedItems") {
const auto fully_resolved{find_adjacent_dependencies(
subschema, entry.second, {"prefixItems", "items", "contains"},
dependencies)};
schema, pair.first, frame, entry.second, walker, resolver,
{"prefixItems", "items", "contains"}, dependencies)};
result.emplace(keyword_uri, UnevaluatedEntry{std::move(dependencies),
!fully_resolved});
} else if (subschema_vocabularies.contains(
"https://json-schema.org/draft/2019-09/vocab/"
"applicator") &&
pair.first == "unevaluatedProperties") {
const auto fully_resolved{find_adjacent_dependencies(
subschema, entry.second,
schema, pair.first, frame, entry.second, walker, resolver,
{"properties", "patternProperties", "additionalProperties"},
dependencies)};
result.emplace(keyword_uri, UnevaluatedEntry{std::move(dependencies),
Expand All @@ -88,8 +153,8 @@ auto unevaluated(const JSON &schema, const FrameLocations &frame,
"applicator") &&
pair.first == "unevaluatedItems") {
const auto fully_resolved{find_adjacent_dependencies(
subschema, entry.second, {"items", "additionalItems"},
dependencies)};
schema, pair.first, frame, entry.second, walker, resolver,
{"items", "additionalItems"}, dependencies)};
result.emplace(keyword_uri, UnevaluatedEntry{std::move(dependencies),
!fully_resolved});
}
Expand Down
6 changes: 3 additions & 3 deletions test/jsonschema/jsonschema_unevaluated_2019_09_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ TEST(JSONSchema_unevaluated_2019_09, unevaluatedProperties_1) {

EXPECT_EQ(result.size(), 1);

EXPECT_UNEVALUATED_DYNAMIC(result, "#/unevaluatedProperties", 3);
EXPECT_UNEVALUATED_STATIC(result, "#/unevaluatedProperties", 3);
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedProperties",
"/properties");
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedProperties",
Expand Down Expand Up @@ -56,8 +56,8 @@ TEST(JSONSchema_unevaluated_2019_09, unevaluatedProperties_2) {

EXPECT_EQ(result.size(), 1);

EXPECT_UNEVALUATED_DYNAMIC(result,
"https://example.com#/unevaluatedProperties", 3);
EXPECT_UNEVALUATED_STATIC(result,
"https://example.com#/unevaluatedProperties", 3);
EXPECT_UNEVALUATED_DEPENDENCY(
result, "https://example.com#/unevaluatedProperties", "/properties");
EXPECT_UNEVALUATED_DEPENDENCY(result,
Expand Down
34 changes: 30 additions & 4 deletions test/jsonschema/jsonschema_unevaluated_2020_12_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ TEST(JSONSchema_unevaluated_2020_12, unevaluatedProperties_1) {

EXPECT_EQ(result.size(), 1);

EXPECT_UNEVALUATED_DYNAMIC(result, "#/unevaluatedProperties", 3);
EXPECT_UNEVALUATED_STATIC(result, "#/unevaluatedProperties", 3);
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedProperties",
"/properties");
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedProperties",
Expand Down Expand Up @@ -56,8 +56,8 @@ TEST(JSONSchema_unevaluated_2020_12, unevaluatedProperties_2) {

EXPECT_EQ(result.size(), 1);

EXPECT_UNEVALUATED_DYNAMIC(result,
"https://example.com#/unevaluatedProperties", 3);
EXPECT_UNEVALUATED_STATIC(result,
"https://example.com#/unevaluatedProperties", 3);
EXPECT_UNEVALUATED_DEPENDENCY(
result, "https://example.com#/unevaluatedProperties", "/properties");
EXPECT_UNEVALUATED_DEPENDENCY(result,
Expand All @@ -68,6 +68,32 @@ TEST(JSONSchema_unevaluated_2020_12, unevaluatedProperties_2) {
"/additionalProperties");
}

TEST(JSONSchema_unevaluated_2020_12, unevaluatedProperties_3) {
const auto schema = sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": { "foo": true },
"allOf": [ { "patternProperties": { "^@": true } } ],
"unevaluatedProperties": false
})JSON");

sourcemeta::jsontoolkit::FrameLocations frame;
sourcemeta::jsontoolkit::FrameReferences references;
sourcemeta::jsontoolkit::frame(schema, frame, references,
sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver);
const auto result{sourcemeta::jsontoolkit::unevaluated(
schema, frame, references, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver)};

EXPECT_EQ(result.size(), 1);

EXPECT_UNEVALUATED_STATIC(result, "#/unevaluatedProperties", 2);
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedProperties",
"/properties");
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedProperties",
"/allOf/0/patternProperties");
}

TEST(JSONSchema_unevaluated_2020_12, unevaluatedItems_1) {
const auto schema = sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
Expand All @@ -88,7 +114,7 @@ TEST(JSONSchema_unevaluated_2020_12, unevaluatedItems_1) {

EXPECT_EQ(result.size(), 1);

EXPECT_UNEVALUATED_DYNAMIC(result, "#/unevaluatedItems", 3);
EXPECT_UNEVALUATED_STATIC(result, "#/unevaluatedItems", 3);
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedItems", "/prefixItems");
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedItems", "/items");
EXPECT_UNEVALUATED_DEPENDENCY(result, "#/unevaluatedItems", "/contains");
Expand Down

0 comments on commit 0dc5725

Please sign in to comment.