Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/compiler/compile_output_simple.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ auto SimpleOutput::operator()(
return evaluate_path.starts_with(entry.first) &&
!entry.second;
})) {
// Within a contains context, we suppress the error, but we still
// need to drop annotations for items that failed the subschema.
// Otherwise, annotations from non-matching items are incorrectly
// retained.
if (type == EvaluationType::Post && !this->annotations_.empty()) {
for (auto iterator = this->annotations_.begin();
iterator != this->annotations_.end();) {
if (iterator->first.evaluate_path.starts_with_initial(evaluate_path) &&
iterator->first.instance_location.starts_with(instance_location)) {
iterator = this->annotations_.erase(iterator);
} else {
iterator++;
}
}
}

return;
}

Expand Down
118 changes: 118 additions & 0 deletions test/compiler/compiler_output_simple_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -915,3 +915,121 @@ TEST(Compiler_output_simple, fail_stacktrace_with_indentation) {
at evaluate path "/properties/foo/unevaluatedProperties"
)JSON");
}

TEST(Compiler_output_simple, annotations_contains_drops_failed_items_1) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contains": {
"type": "number",
"title": "Test"
}
})JSON")};

const auto schema_template{sourcemeta::blaze::compile(
schema, sourcemeta::core::schema_official_walker,
sourcemeta::core::schema_official_resolver,
sourcemeta::blaze::default_schema_compiler,
sourcemeta::blaze::Mode::Exhaustive)};

const sourcemeta::core::JSON instance{
sourcemeta::core::parse_json(R"JSON([ "foo", 42, true ])JSON")};

sourcemeta::blaze::SimpleOutput output{instance};
sourcemeta::blaze::Evaluator evaluator;
const auto result{
evaluator.validate(schema_template, instance, std::ref(output))};
EXPECT_TRUE(result);

// The title annotation should only be retained for /1 (42),
// not for /0 ("foo") or /2 (true) which fail the contains subschema
EXPECT_ANNOTATION_COUNT(output, 2);

EXPECT_ANNOTATION_ENTRY(output, "", "/contains", "#/contains", 1);
EXPECT_ANNOTATION_VALUE(output, "", "/contains", "#/contains", 0,
sourcemeta::core::JSON{1});

EXPECT_ANNOTATION_ENTRY(output, "/1", "/contains/title", "#/contains/title",
1);
EXPECT_ANNOTATION_VALUE(output, "/1", "/contains/title", "#/contains/title",
0, sourcemeta::core::JSON{"Test"});
}

TEST(Compiler_output_simple, annotations_contains_drops_failed_items_2) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contains": {
"type": "number",
"title": "Test"
}
})JSON")};

const auto schema_template{sourcemeta::blaze::compile(
schema, sourcemeta::core::schema_official_walker,
sourcemeta::core::schema_official_resolver,
sourcemeta::blaze::default_schema_compiler,
sourcemeta::blaze::Mode::Exhaustive)};

const sourcemeta::core::JSON instance{
sourcemeta::core::parse_json(R"JSON([ 1, 2, 3 ])JSON")};

sourcemeta::blaze::SimpleOutput output{instance};
sourcemeta::blaze::Evaluator evaluator;
const auto result{
evaluator.validate(schema_template, instance, std::ref(output))};
EXPECT_TRUE(result);

// All items match, so all title annotations should be retained
EXPECT_ANNOTATION_COUNT(output, 4);

EXPECT_ANNOTATION_ENTRY(output, "", "/contains", "#/contains", 3);
EXPECT_ANNOTATION_VALUE(output, "", "/contains", "#/contains", 0,
sourcemeta::core::JSON{0});
EXPECT_ANNOTATION_VALUE(output, "", "/contains", "#/contains", 1,
sourcemeta::core::JSON{1});
EXPECT_ANNOTATION_VALUE(output, "", "/contains", "#/contains", 2,
sourcemeta::core::JSON{2});

EXPECT_ANNOTATION_ENTRY(output, "/0", "/contains/title", "#/contains/title",
1);
EXPECT_ANNOTATION_VALUE(output, "/0", "/contains/title", "#/contains/title",
0, sourcemeta::core::JSON{"Test"});

EXPECT_ANNOTATION_ENTRY(output, "/1", "/contains/title", "#/contains/title",
1);
EXPECT_ANNOTATION_VALUE(output, "/1", "/contains/title", "#/contains/title",
0, sourcemeta::core::JSON{"Test"});

EXPECT_ANNOTATION_ENTRY(output, "/2", "/contains/title", "#/contains/title",
1);
EXPECT_ANNOTATION_VALUE(output, "/2", "/contains/title", "#/contains/title",
0, sourcemeta::core::JSON{"Test"});
}

TEST(Compiler_output_simple, annotations_contains_drops_failed_items_3) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contains": {
"type": "number",
"title": "Test"
}
})JSON")};

const auto schema_template{sourcemeta::blaze::compile(
schema, sourcemeta::core::schema_official_walker,
sourcemeta::core::schema_official_resolver,
sourcemeta::blaze::default_schema_compiler,
sourcemeta::blaze::Mode::Exhaustive)};

const sourcemeta::core::JSON instance{
sourcemeta::core::parse_json(R"JSON([ "foo", "bar" ])JSON")};

sourcemeta::blaze::SimpleOutput output{instance};
sourcemeta::blaze::Evaluator evaluator;
const auto result{
evaluator.validate(schema_template, instance, std::ref(output))};
EXPECT_FALSE(result);

// All items fail the contains subschema, and contains itself fails,
// so no annotations should be retained
EXPECT_ANNOTATION_COUNT(output, 0);
}