Skip to content

Commit

Permalink
Implement a new LoopPropertiesNoAdjacentAnnotation compile step
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti committed Aug 30, 2024
1 parent 7789be6 commit 7b52afe
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 65 deletions.
22 changes: 18 additions & 4 deletions src/jsonschema/compile_describe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -589,11 +589,25 @@ struct DescribeVisitor {
assert(this->keyword == "additionalProperties");
std::ostringstream message;
if (step.children.size() == 1 &&
std::holds_alternative<SchemaCompilerLogicalAnd>(
step.children.front()) &&
std::holds_alternative<SchemaCompilerAssertionFail>(
std::get<SchemaCompilerLogicalAnd>(step.children.front())
.children.front())) {
step.children.front())) {
message << "The object value was not expected to define additional "
"properties";
} else {
message << "The object properties not covered by other adjacent object "
"keywords were expected to validate against this subschema";
}

return message.str();
}

auto operator()(const SchemaCompilerLoopPropertiesNoAdjacentAnnotation &step)
const -> std::string {
assert(this->keyword == "additionalProperties");
std::ostringstream message;
if (step.children.size() == 1 &&
std::holds_alternative<SchemaCompilerAssertionFail>(
step.children.front())) {
message << "The object value was not expected to define additional "
"properties";
} else {
Expand Down
59 changes: 59 additions & 0 deletions src/jsonschema/compile_evaluate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,65 @@ auto evaluate_step(

evaluate_loop_properties_regex_end:
CALLBACK_POST("SchemaCompilerLoopPropertiesRegex", loop);
} else if (std::holds_alternative<
SchemaCompilerLoopPropertiesNoAdjacentAnnotation>(step)) {
SOURCEMETA_TRACE_START(trace_id,
"SchemaCompilerLoopPropertiesNoAdjacentAnnotation");
const auto &loop{
std::get<SchemaCompilerLoopPropertiesNoAdjacentAnnotation>(step)};
context.push(loop);
EVALUATE_CONDITION_GUARD("SchemaCompilerLoopPropertiesNoAdjacentAnnotation",
loop, instance);
const auto &target{context.resolve_target<JSON>(loop.target, instance)};
EVALUATE_IMPLICIT_PRECONDITION(
"SchemaCompilerLoopPropertiesNoAdjacentAnnotation", loop,
target.is_object());
CALLBACK_PRE(loop, context.instance_location());
result = true;
const auto &value{context.resolve_value(loop.value, instance)};
assert(!value.empty());

// TODO: Find a way to be more efficient with this
std::vector<std::reference_wrapper<const EvaluationContext::Annotations>>
current_annotations;
for (const auto &keyword : value) {
assert(!context.evaluate_path().empty());
// TODO: Can we avoid this expensive pointer manipulation?
auto expected_evaluate_path{context.evaluate_path()};
expected_evaluate_path.pop_back();
expected_evaluate_path.push_back({keyword});
current_annotations.emplace_back(context.annotations(
context.instance_location(), expected_evaluate_path));
}

for (const auto &entry : target.as_object()) {
bool apply_children{true};
for (const auto &annotations : current_annotations) {
if (annotations.get().contains(JSON{entry.first})) {
apply_children = false;
break;
}
}

if (!apply_children) {
continue;
}

context.push(loop, empty_pointer, {entry.first});
for (const auto &child : loop.children) {
if (!evaluate_step(child, instance, mode, callback, context)) {
result = false;
context.pop(loop);
// For efficiently breaking from the outer loop too
goto evaluate_loop_properties_no_adjacent_annotation_end;
}
}

context.pop(loop);
}

evaluate_loop_properties_no_adjacent_annotation_end:
CALLBACK_POST("SchemaCompilerLoopPropertiesNoAdjacentAnnotation", loop);
} else if (std::holds_alternative<SchemaCompilerLoopKeys>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerLoopKeys");
const auto &loop{std::get<SchemaCompilerLoopKeys>(step)};
Expand Down
2 changes: 2 additions & 0 deletions src/jsonschema/compile_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ struct StepVisitor {
HANDLE_STEP("loop", "properties-match", SchemaCompilerLoopPropertiesMatch)
HANDLE_STEP("loop", "properties", SchemaCompilerLoopProperties)
HANDLE_STEP("loop", "properties-regex", SchemaCompilerLoopPropertiesRegex)
HANDLE_STEP("loop", "properties-no-adjacent-annotation",
SchemaCompilerLoopPropertiesNoAdjacentAnnotation)
HANDLE_STEP("loop", "keys", SchemaCompilerLoopKeys)
HANDLE_STEP("loop", "items", SchemaCompilerLoopItems)
HANDLE_STEP("loop", "items-from-annotation-index",
Expand Down
51 changes: 18 additions & 33 deletions src/jsonschema/default_compiler_draft4.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,29 +473,6 @@ auto compiler_draft4_applicator_additionalproperties_conditional_annotation(
const SchemaCompilerSchemaContext &schema_context,
const SchemaCompilerDynamicContext &dynamic_context,
const bool annotate) -> SchemaCompilerTemplate {
// Evaluate the subschema against the current property if it
// was NOT collected as an annotation on either "properties" or
// "patternProperties"

// TODO: Extend SchemaCompilerAssertionNoAdjacentAnnotation to take
// more than one path and check on both on one shot so we save 2
// instructions
SchemaCompilerTemplate conjunctions{
make<SchemaCompilerAssertionNoAdjacentAnnotation>(
false, context, schema_context, relative_dynamic_context,
SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename,
empty_pointer},
{}, SchemaCompilerTargetType::ParentAdjacentAnnotations,
Pointer{"properties"}),

make<SchemaCompilerAssertionNoAdjacentAnnotation>(
false, context, schema_context, relative_dynamic_context,
SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename,
empty_pointer},
{}, SchemaCompilerTargetType::ParentAdjacentAnnotations,
Pointer{"patternProperties"}),
};

SchemaCompilerTemplate children{compile(context, schema_context,
relative_dynamic_context,
empty_pointer, empty_pointer)};
Expand All @@ -506,17 +483,25 @@ auto compiler_draft4_applicator_additionalproperties_conditional_annotation(
SchemaCompilerValueNone{}, {}, SchemaCompilerTargetType::Instance));
}

SchemaCompilerTemplate wrapper{make<SchemaCompilerLogicalAnd>(
false, context, schema_context, relative_dynamic_context,
SchemaCompilerValueNone{}, std::move(children),
{make<SchemaCompilerLogicalAnd>(
true, context, schema_context, relative_dynamic_context,
SchemaCompilerValueNone{}, std::move(conjunctions),
SchemaCompilerTemplate{})})};
SchemaCompilerValueStrings dependencies;
if (schema_context.schema.defines("properties")) {
dependencies.insert("properties");
}

return {make<SchemaCompilerLoopProperties>(
true, context, schema_context, dynamic_context, SchemaCompilerValueNone{},
{std::move(wrapper)}, SchemaCompilerTemplate{})};
if (schema_context.schema.defines("patternProperties")) {
dependencies.insert("patternProperties");
}

if (dependencies.empty()) {
return {make<SchemaCompilerLoopProperties>(
true, context, schema_context, dynamic_context,
SchemaCompilerValueNone{}, std::move(children),
SchemaCompilerTemplate{})};
} else {
return {make<SchemaCompilerLoopPropertiesNoAdjacentAnnotation>(
true, context, schema_context, dynamic_context, std::move(dependencies),
std::move(children), SchemaCompilerTemplate{})};
}
}

auto compiler_draft4_applicator_additionalproperties(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ struct SchemaCompilerLoopProperties;
/// given ECMA regular expression
struct SchemaCompilerLoopPropertiesRegex;

/// @ingroup jsonschema
/// Represents a compiler step that loops over object properties that were
/// not collected as adjacent annotations
struct SchemaCompilerLoopPropertiesNoAdjacentAnnotation;

/// @ingroup jsonschema
/// Represents a compiler step that loops over object property keys
struct SchemaCompilerLoopKeys;
Expand Down Expand Up @@ -348,7 +353,8 @@ using SchemaCompilerTemplate = std::vector<std::variant<
SchemaCompilerLogicalTry, SchemaCompilerLogicalNot,
SchemaCompilerLogicalWhenType, SchemaCompilerLogicalWhenDefines,
SchemaCompilerLoopPropertiesMatch, SchemaCompilerLoopProperties,
SchemaCompilerLoopPropertiesRegex, SchemaCompilerLoopKeys,
SchemaCompilerLoopPropertiesRegex,
SchemaCompilerLoopPropertiesNoAdjacentAnnotation, SchemaCompilerLoopKeys,
SchemaCompilerLoopItems, SchemaCompilerLoopItemsFromAnnotationIndex,
SchemaCompilerLoopContains, SchemaCompilerControlLabel,
SchemaCompilerControlMark, SchemaCompilerControlJump,
Expand Down Expand Up @@ -447,6 +453,8 @@ DEFINE_STEP_APPLICATOR(Logical, WhenDefines, SchemaCompilerValueString)
DEFINE_STEP_APPLICATOR(Loop, PropertiesMatch, SchemaCompilerValueNamedIndexes)
DEFINE_STEP_APPLICATOR(Loop, Properties, SchemaCompilerValueNone)
DEFINE_STEP_APPLICATOR(Loop, PropertiesRegex, SchemaCompilerValueRegex)
DEFINE_STEP_APPLICATOR(Loop, PropertiesNoAdjacentAnnotation,
SchemaCompilerValueStrings)
DEFINE_STEP_APPLICATOR(Loop, Keys, SchemaCompilerValueNone)
DEFINE_STEP_APPLICATOR(Loop, Items, SchemaCompilerValueUnsignedInteger)
DEFINE_STEP_APPLICATOR(Loop, ItemsFromAnnotationIndex,
Expand Down
18 changes: 10 additions & 8 deletions test/jsonschema/jsonschema_compile_2019_09_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,8 @@ TEST(JSONSchema_compile_2019_09, additionalProperties_2_fast) {
EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/properties/foo/type",
"#/properties/foo/type", "/foo");
EVALUATE_TRACE_PRE_ANNOTATION(2, "/properties", "#/properties", "");
EVALUATE_TRACE_PRE(3, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_PRE(3, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties", "");
EVALUATE_TRACE_PRE(4, AssertionType, "/additionalProperties/type",
"#/additionalProperties/type", "/bar");
EVALUATE_TRACE_PRE_ANNOTATION(5, "/additionalProperties",
Expand All @@ -500,8 +500,9 @@ TEST(JSONSchema_compile_2019_09, additionalProperties_2_fast) {
"#/additionalProperties/type", "/bar");
EVALUATE_TRACE_POST_ANNOTATION(4, "/additionalProperties",
"#/additionalProperties", "", "bar");
EVALUATE_TRACE_POST_SUCCESS(5, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_POST_SUCCESS(5, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties",
"");

EVALUATE_TRACE_POST_DESCRIBE(instance, 0,
"The value was expected to be of type boolean");
Expand Down Expand Up @@ -552,8 +553,8 @@ TEST(JSONSchema_compile_2019_09, additionalProperties_2_exhaustive) {
EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/properties/foo/type",
"#/properties/foo/type", "/foo");
EVALUATE_TRACE_PRE_ANNOTATION(2, "/properties", "#/properties", "");
EVALUATE_TRACE_PRE(3, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_PRE(3, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties", "");
EVALUATE_TRACE_PRE(4, AssertionType, "/additionalProperties/type",
"#/additionalProperties/type", "/bar");
EVALUATE_TRACE_PRE_ANNOTATION(5, "/additionalProperties",
Expand All @@ -568,8 +569,9 @@ TEST(JSONSchema_compile_2019_09, additionalProperties_2_exhaustive) {
"#/additionalProperties/type", "/bar");
EVALUATE_TRACE_POST_ANNOTATION(4, "/additionalProperties",
"#/additionalProperties", "", "bar");
EVALUATE_TRACE_POST_SUCCESS(5, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_POST_SUCCESS(5, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties",
"");

EVALUATE_TRACE_POST_DESCRIBE(instance, 0,
"The value was expected to be of type boolean");
Expand Down
44 changes: 25 additions & 19 deletions test/jsonschema/jsonschema_compile_draft4_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1639,8 +1639,8 @@ TEST(JSONSchema_compile_draft4, additionalProperties_2) {
EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/properties/foo/type",
"#/properties/foo/type", "/foo");
EVALUATE_TRACE_PRE_ANNOTATION(2, "/properties", "#/properties", "");
EVALUATE_TRACE_PRE(3, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_PRE(3, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties", "");
EVALUATE_TRACE_PRE(4, AssertionTypeStrict, "/additionalProperties/type",
"#/additionalProperties/type", "/bar");

Expand All @@ -1652,8 +1652,9 @@ TEST(JSONSchema_compile_draft4, additionalProperties_2) {
EVALUATE_TRACE_POST_SUCCESS(3, AssertionTypeStrict,
"/additionalProperties/type",
"#/additionalProperties/type", "/bar");
EVALUATE_TRACE_POST_SUCCESS(4, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_POST_SUCCESS(4, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties",
"");

EVALUATE_TRACE_POST_DESCRIBE(instance, 0,
"The value was expected to be of type boolean");
Expand Down Expand Up @@ -1697,8 +1698,8 @@ TEST(JSONSchema_compile_draft4, additionalProperties_3) {
EVALUATE_WITH_TRACE_FAST_SUCCESS(compiled_schema, instance, 4);

EVALUATE_TRACE_PRE(0, LoopPropertiesMatch, "/properties", "#/properties", "");
EVALUATE_TRACE_PRE(1, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_PRE(1, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties", "");
EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/additionalProperties/type",
"#/additionalProperties/type", "/bar");
EVALUATE_TRACE_PRE(3, AssertionTypeStrict, "/additionalProperties/type",
Expand All @@ -1712,8 +1713,9 @@ TEST(JSONSchema_compile_draft4, additionalProperties_3) {
EVALUATE_TRACE_POST_SUCCESS(2, AssertionTypeStrict,
"/additionalProperties/type",
"#/additionalProperties/type", "/foo");
EVALUATE_TRACE_POST_SUCCESS(3, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_POST_SUCCESS(3, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties",
"");

EVALUATE_TRACE_POST_DESCRIBE(instance, 0,
"The object value was expected to validate "
Expand Down Expand Up @@ -1765,8 +1767,8 @@ TEST(JSONSchema_compile_draft4, additionalProperties_4) {
"#/properties/foo/type", "/foo");
EVALUATE_TRACE_PRE_ANNOTATION(5, "/properties", "#/properties", "");

EVALUATE_TRACE_PRE(6, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_PRE(6, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties", "");
EVALUATE_TRACE_PRE(7, AssertionTypeStrict, "/additionalProperties/type",
"#/additionalProperties/type", "/baz");

Expand All @@ -1791,8 +1793,9 @@ TEST(JSONSchema_compile_draft4, additionalProperties_4) {
EVALUATE_TRACE_POST_SUCCESS(6, AssertionTypeStrict,
"/additionalProperties/type",
"#/additionalProperties/type", "/baz");
EVALUATE_TRACE_POST_SUCCESS(7, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_POST_SUCCESS(7, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties",
"");

EVALUATE_TRACE_POST_DESCRIBE(instance, 0,
"The value was expected to be of type integer");
Expand Down Expand Up @@ -1847,8 +1850,8 @@ TEST(JSONSchema_compile_draft4, additionalProperties_5) {
EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/properties/foo/type",
"#/properties/foo/type", "/foo");
EVALUATE_TRACE_PRE_ANNOTATION(2, "/properties", "#/properties", "");
EVALUATE_TRACE_PRE(3, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_PRE(3, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties", "");
EVALUATE_TRACE_PRE(4, AssertionFail, "/additionalProperties",
"#/additionalProperties", "/bar");

Expand All @@ -1859,8 +1862,9 @@ TEST(JSONSchema_compile_draft4, additionalProperties_5) {
"#/properties", "");
EVALUATE_TRACE_POST_FAILURE(3, AssertionFail, "/additionalProperties",
"#/additionalProperties", "/bar");
EVALUATE_TRACE_POST_FAILURE(4, LoopProperties, "/additionalProperties",
"#/additionalProperties", "");
EVALUATE_TRACE_POST_FAILURE(4, LoopPropertiesNoAdjacentAnnotation,
"/additionalProperties", "#/additionalProperties",
"");

EVALUATE_TRACE_POST_DESCRIBE(instance, 0,
"The value was expected to be of type boolean");
Expand Down Expand Up @@ -1976,8 +1980,9 @@ TEST(JSONSchema_compile_draft4, not_3) {
EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/not/properties/foo/type",
"#/not/properties/foo/type", "/foo");
EVALUATE_TRACE_PRE_ANNOTATION(3, "/not/properties", "#/not/properties", "");
EVALUATE_TRACE_PRE(4, LoopProperties, "/not/additionalProperties",
"#/not/additionalProperties", "");
EVALUATE_TRACE_PRE(4, LoopPropertiesNoAdjacentAnnotation,
"/not/additionalProperties", "#/not/additionalProperties",
"");
EVALUATE_TRACE_PRE(5, AssertionTypeStrict, "/not/additionalProperties/type",
"#/not/additionalProperties/type", "/bar");

Expand All @@ -1991,7 +1996,8 @@ TEST(JSONSchema_compile_draft4, not_3) {
EVALUATE_TRACE_POST_FAILURE(3, AssertionTypeStrict,
"/not/additionalProperties/type",
"#/not/additionalProperties/type", "/bar");
EVALUATE_TRACE_POST_FAILURE(4, LoopProperties, "/not/additionalProperties",
EVALUATE_TRACE_POST_FAILURE(4, LoopPropertiesNoAdjacentAnnotation,
"/not/additionalProperties",
"#/not/additionalProperties", "");
EVALUATE_TRACE_POST_SUCCESS(5, LogicalNot, "/not", "#/not", "");

Expand Down

0 comments on commit 7b52afe

Please sign in to comment.