Skip to content

Commit

Permalink
Implement a new AnnotationWhenArraySizeGreater compiler step (#1098)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti authored Sep 2, 2024
1 parent 446c8a5 commit b29de65
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 36 deletions.
28 changes: 24 additions & 4 deletions src/jsonschema/compile_describe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,30 @@ struct DescribeVisitor {
return unknown();
}

auto operator()(const SchemaCompilerAnnotationWhenArraySizeGreater &) const
-> std::string {
if ((this->keyword == "prefixItems" || this->keyword == "items") &&
this->annotation.is_integer()) {
assert(this->target.is_array());
assert(this->annotation.is_positive());
std::ostringstream message;
if (this->annotation.to_integer() == 0) {
message << "The first item of the array value successfully validated "
"against the first "
"positional subschema";
} else {
message << "The first " << this->annotation.to_integer() + 1
<< " items of the array value successfully validated against "
"the given "
"positional subschemas";
}

return message.str();
}

return unknown();
}

auto
operator()(const SchemaCompilerAnnotationToParent &) const -> std::string {
if (this->keyword == "unevaluatedItems" && this->annotation.is_boolean() &&
Expand Down Expand Up @@ -1578,10 +1602,6 @@ struct DescribeVisitor {
return unknown();
}
auto
operator()(const SchemaCompilerAssertionSizeEqual &) const -> std::string {
return unknown();
}
auto
operator()(const SchemaCompilerLoopPropertiesRegex &) const -> std::string {
return unknown();
}
Expand Down
36 changes: 21 additions & 15 deletions src/jsonschema/compile_evaluate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -507,18 +507,6 @@ auto evaluate_step(
result = (target.is_array() || target.is_object() || target.is_string()) &&
(target.size() < assertion.value);
CALLBACK_POST("SchemaCompilerAssertionSizeLess", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionSizeEqual>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionSizeEqual");
const auto &assertion{std::get<SchemaCompilerAssertionSizeEqual>(step)};
context.push(assertion);
// TODO: Get rid of this
EVALUATE_CONDITION_GUARD("SchemaCompilerAssertionSizeEqual", assertion,
instance);
CALLBACK_PRE(assertion, context.instance_location());
const auto &target{context.resolve_target(instance)};
result = (target.is_array() || target.is_object() || target.is_string()) &&
(target.size() == assertion.value);
CALLBACK_POST("SchemaCompilerAssertionSizeEqual", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionEqual>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionEqual");
const auto &assertion{std::get<SchemaCompilerAssertionEqual>(step)};
Expand Down Expand Up @@ -909,9 +897,6 @@ auto evaluate_step(
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAnnotationEmit");
const auto &annotation{std::get<SchemaCompilerAnnotationEmit>(step)};
context.push(annotation);
// TODO: Get rid of this
EVALUATE_CONDITION_GUARD("SchemaCompilerAnnotationEmit", annotation,
instance);
// Annotations never fail
result = true;
const auto &current_instance_location{context.instance_location()};
Expand Down Expand Up @@ -942,6 +927,27 @@ auto evaluate_step(
SOURCEMETA_TRACE_END(trace_id,
"SchemaCompilerAnnotationWhenArraySizeEqual");
return result;
} else if (std::holds_alternative<
SchemaCompilerAnnotationWhenArraySizeGreater>(step)) {
SOURCEMETA_TRACE_START(trace_id,
"SchemaCompilerAnnotationWhenArraySizeGreater");
const auto &annotation{
std::get<SchemaCompilerAnnotationWhenArraySizeGreater>(step)};
context.push(annotation);
const auto &target{context.resolve_target(instance)};
EVALUATE_IMPLICIT_PRECONDITION(
"SchemaCompilerAnnotationWhenArraySizeGreater", annotation,
target.is_array() && target.size() > annotation.value.first);
// Annotations never fail
result = true;
const auto &current_instance_location{context.instance_location()};
const auto value{
context.annotate(current_instance_location, annotation.value.second)};
CALLBACK_ANNOTATION(value, annotation, current_instance_location);
context.pop(annotation);
SOURCEMETA_TRACE_END(trace_id,
"SchemaCompilerAnnotationWhenArraySizeGreater");
return result;
} else if (std::holds_alternative<SchemaCompilerAnnotationToParent>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAnnotationToParent");
const auto &annotation{std::get<SchemaCompilerAnnotationToParent>(step)};
Expand Down
3 changes: 2 additions & 1 deletion src/jsonschema/compile_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,11 @@ struct StepVisitor {
HANDLE_STEP("assertion", "divisible", SchemaCompilerAssertionDivisible)
HANDLE_STEP("assertion", "string-type", SchemaCompilerAssertionStringType)
HANDLE_STEP("assertion", "equals-any", SchemaCompilerAssertionEqualsAny)
HANDLE_STEP("assertion", "size-equal", SchemaCompilerAssertionSizeEqual)
HANDLE_STEP("annotation", "emit", SchemaCompilerAnnotationEmit)
HANDLE_STEP("annotation", "when-array-size-equal",
SchemaCompilerAnnotationWhenArraySizeEqual)
HANDLE_STEP("annotation", "when-array-size-greater",
SchemaCompilerAnnotationWhenArraySizeGreater)
HANDLE_STEP("annotation", "to-parent", SchemaCompilerAnnotationToParent)
HANDLE_STEP("annotation", "basename-to-parent",
SchemaCompilerAnnotationBasenameToParent)
Expand Down
11 changes: 4 additions & 7 deletions src/jsonschema/default_compiler_draft4.h
Original file line number Diff line number Diff line change
Expand Up @@ -633,12 +633,11 @@ auto compiler_draft4_applicator_items_array(
true, context, schema_context, relative_dynamic_context,
SchemaCompilerValueIndexedJSON{cursor, JSON{true}},
SchemaCompilerTemplate{}));
subchildren.push_back(make<SchemaCompilerAnnotationEmit>(
true, context, schema_context, relative_dynamic_context,
JSON{cursor - 1},
{make<SchemaCompilerAssertionSizeGreater>(
subchildren.push_back(
make<SchemaCompilerAnnotationWhenArraySizeGreater>(
true, context, schema_context, relative_dynamic_context,
SchemaCompilerValueUnsignedInteger{cursor}, {})}));
SchemaCompilerValueIndexedJSON{cursor, JSON{cursor - 1}},
SchemaCompilerTemplate{}));
}

children.push_back(make<SchemaCompilerLogicalWhenArraySizeGreater>(
Expand All @@ -659,8 +658,6 @@ auto compiler_draft4_applicator_items_array(
}
}

// TODO: Eventually make this a LogicalAnd, as there
// will be array checks in all the substeps
return {make<SchemaCompilerLogicalWhenType>(
true, context, schema_context, dynamic_context, JSON::Type::Array,
std::move(children), SchemaCompilerTemplate{})};
Expand Down
19 changes: 10 additions & 9 deletions src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,6 @@ struct SchemaCompilerAssertionDivisible;
/// certain type
struct SchemaCompilerAssertionStringType;

/// @ingroup jsonschema
/// Represents a compiler assertion step that checks a given array, object, or
/// string has a certain number of items, properties, or characters,
/// respectively
struct SchemaCompilerAssertionSizeEqual;

/// @ingroup jsonschema
/// Represents a compiler step that emits an annotation
struct SchemaCompilerAnnotationEmit;
Expand All @@ -217,6 +211,11 @@ struct SchemaCompilerAnnotationEmit;
/// array instance is equal to the given size
struct SchemaCompilerAnnotationWhenArraySizeEqual;

/// @ingroup jsonschema
/// Represents a compiler step that emits an annotation when the size of the
/// array instance is greater than the given size
struct SchemaCompilerAnnotationWhenArraySizeGreater;

/// @ingroup jsonschema
/// Represents a compiler step that emits an annotation to the parent
struct SchemaCompilerAnnotationToParent;
Expand Down Expand Up @@ -354,8 +353,9 @@ using SchemaCompilerTemplate = std::vector<std::variant<
SchemaCompilerAssertionGreaterEqual, SchemaCompilerAssertionLessEqual,
SchemaCompilerAssertionGreater, SchemaCompilerAssertionLess,
SchemaCompilerAssertionUnique, SchemaCompilerAssertionDivisible,
SchemaCompilerAssertionStringType, SchemaCompilerAssertionSizeEqual,
SchemaCompilerAnnotationEmit, SchemaCompilerAnnotationWhenArraySizeEqual,
SchemaCompilerAssertionStringType, SchemaCompilerAnnotationEmit,
SchemaCompilerAnnotationWhenArraySizeEqual,
SchemaCompilerAnnotationWhenArraySizeGreater,
SchemaCompilerAnnotationToParent, SchemaCompilerAnnotationBasenameToParent,
SchemaCompilerLogicalOr, SchemaCompilerLogicalAnd, SchemaCompilerLogicalXor,
SchemaCompilerLogicalTryMark, SchemaCompilerLogicalNot,
Expand Down Expand Up @@ -420,10 +420,11 @@ DEFINE_STEP_WITH_VALUE(Assertion, Less, SchemaCompilerValueJSON)
DEFINE_STEP_WITH_VALUE(Assertion, Unique, SchemaCompilerValueNone)
DEFINE_STEP_WITH_VALUE(Assertion, Divisible, SchemaCompilerValueJSON)
DEFINE_STEP_WITH_VALUE(Assertion, StringType, SchemaCompilerValueStringType)
DEFINE_STEP_WITH_VALUE(Assertion, SizeEqual, SchemaCompilerValueUnsignedInteger)
DEFINE_STEP_WITH_VALUE(Annotation, Emit, SchemaCompilerValueJSON)
DEFINE_STEP_WITH_VALUE(Annotation, WhenArraySizeEqual,
SchemaCompilerValueIndexedJSON)
DEFINE_STEP_WITH_VALUE(Annotation, WhenArraySizeGreater,
SchemaCompilerValueIndexedJSON)
DEFINE_STEP_WITH_VALUE(Annotation, ToParent, SchemaCompilerValueJSON)
DEFINE_STEP_WITH_VALUE(Annotation, BasenameToParent, SchemaCompilerValueNone)
DEFINE_STEP_APPLICATOR(Logical, Or, SchemaCompilerValueBoolean)
Expand Down
12 changes: 12 additions & 0 deletions test/jsonschema/jsonschema_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@
std::get<3>(trace_pre.at(index)))) { \
EVALUATE_TRACE_PRE(index, AnnotationWhenArraySizeEqual, evaluate_path, \
keyword_location, instance_location); \
} else if (std::holds_alternative< \
sourcemeta::jsontoolkit:: \
SchemaCompilerAnnotationWhenArraySizeGreater>( \
std::get<3>(trace_pre.at(index)))) { \
EVALUATE_TRACE_PRE(index, AnnotationWhenArraySizeGreater, evaluate_path, \
keyword_location, instance_location); \
} else { \
EVALUATE_TRACE_PRE(index, AnnotationEmit, evaluate_path, keyword_location, \
instance_location); \
Expand All @@ -308,6 +314,12 @@
std::get<3>(trace_post.at(index)))) { \
EVALUATE_TRACE_POST(index, AnnotationWhenArraySizeEqual, evaluate_path, \
keyword_location, instance_location); \
} else if (std::holds_alternative< \
sourcemeta::jsontoolkit:: \
SchemaCompilerAnnotationWhenArraySizeGreater>( \
std::get<3>(trace_post.at(index)))) { \
EVALUATE_TRACE_POST(index, AnnotationWhenArraySizeGreater, evaluate_path, \
keyword_location, instance_location); \
} else { \
EVALUATE_TRACE_POST(index, AnnotationEmit, evaluate_path, \
keyword_location, instance_location); \
Expand Down

4 comments on commit b29de65

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (macos)

Benchmark suite Current: b29de65 Previous: 446c8a5 Ratio
JSONSchema_Compile_Basic 204049.65753429395 ns/iter 201976.72209500815 ns/iter 1.01
JSONSchema_Validate_Draft4_Meta_1_No_Callback 1599.5496768072023 ns/iter 1604.4579549939835 ns/iter 1.00
JSONSchema_Validate_Draft4_Required_Properties 2401.6238914099445 ns/iter 2475.1339894153225 ns/iter 0.97
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 165.32197851504478 ns/iter 167.52181478113985 ns/iter 0.99
JSONSchema_Validate_Draft4_Items_Schema 8964.991558762851 ns/iter 9575.568985114855 ns/iter 0.94

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (linux/llvm)

Benchmark suite Current: b29de65 Previous: 446c8a5 Ratio
JSONSchema_Compile_Basic 391864.60775620455 ns/iter 384167.9646409036 ns/iter 1.02
JSONSchema_Validate_Draft4_Meta_1_No_Callback 16650.35405617414 ns/iter 16835.97709303747 ns/iter 0.99
JSONSchema_Validate_Draft4_Required_Properties 8208.450116630971 ns/iter 7743.500701434632 ns/iter 1.06
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 1563.5008376216072 ns/iter 1560.3980533702218 ns/iter 1.00
JSONSchema_Validate_Draft4_Items_Schema 109444.21641678351 ns/iter 106137.31248114795 ns/iter 1.03

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (linux/gcc)

Benchmark suite Current: b29de65 Previous: 446c8a5 Ratio
JSONSchema_Validate_Draft4_Meta_1_No_Callback 2285.514874075199 ns/iter 2220.5265662275265 ns/iter 1.03
JSONSchema_Validate_Draft4_Required_Properties 2846.202824670392 ns/iter 2808.450689032594 ns/iter 1.01
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 212.87328586916013 ns/iter 204.48548949175242 ns/iter 1.04
JSONSchema_Validate_Draft4_Items_Schema 11551.45066766161 ns/iter 11860.530190857715 ns/iter 0.97
JSONSchema_Compile_Basic 386650.7541436813 ns/iter 395414.15346258995 ns/iter 0.98

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (windows)

Benchmark suite Current: b29de65 Previous: 446c8a5 Ratio
JSONSchema_Compile_Basic 787890.9598212199 ns/iter 775357.0312501219 ns/iter 1.02
JSONSchema_Validate_Draft4_Meta_1_No_Callback 4647.183832524478 ns/iter 4802.316969457869 ns/iter 0.97
JSONSchema_Validate_Draft4_Required_Properties 3760.838284216279 ns/iter 3685.3861607141375 ns/iter 1.02
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 780.9710937500824 ns/iter 810.2354910715094 ns/iter 0.96
JSONSchema_Validate_Draft4_Items_Schema 26074.427958865337 ns/iter 26093.417857144428 ns/iter 1.00

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.