Skip to content

Commit

Permalink
Implement a new AnnotationWhenArraySizeEqual compiler step (#1097)
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 861acee commit 446c8a5
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 22 deletions.
32 changes: 23 additions & 9 deletions src/jsonschema/compile_describe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -279,15 +279,6 @@ struct DescribeVisitor {
return message.str();
}

if (this->keyword == "prefixItems" && this->annotation.is_boolean() &&
this->annotation.to_boolean()) {
assert(this->target.is_array());
std::ostringstream message;
message << "Every item of the array value validated against the given "
"positional subschemas";
return message.str();
}

if ((this->keyword == "prefixItems" || this->keyword == "items") &&
this->annotation.is_integer()) {
assert(this->target.is_array());
Expand Down Expand Up @@ -477,6 +468,29 @@ struct DescribeVisitor {
return message.str();
}

auto operator()(const SchemaCompilerAnnotationWhenArraySizeEqual &) const
-> std::string {
if (this->keyword == "items" && this->annotation.is_boolean() &&
this->annotation.to_boolean()) {
assert(this->target.is_array());
std::ostringstream message;
message << "At least one item of the array value successfully validated "
"against the given subschema";
return message.str();
}

if (this->keyword == "prefixItems" && this->annotation.is_boolean() &&
this->annotation.to_boolean()) {
assert(this->target.is_array());
std::ostringstream message;
message << "Every item of the array value 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
21 changes: 21 additions & 0 deletions src/jsonschema/compile_evaluate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,27 @@ auto evaluate_step(
context.pop(annotation);
SOURCEMETA_TRACE_END(trace_id, "SchemaCompilerAnnotationEmit");
return result;
} else if (std::holds_alternative<SchemaCompilerAnnotationWhenArraySizeEqual>(
step)) {
SOURCEMETA_TRACE_START(trace_id,
"SchemaCompilerAnnotationWhenArraySizeEqual");
const auto &annotation{
std::get<SchemaCompilerAnnotationWhenArraySizeEqual>(step)};
context.push(annotation);
const auto &target{context.resolve_target(instance)};
EVALUATE_IMPLICIT_PRECONDITION(
"SchemaCompilerAnnotationWhenArraySizeEqual", 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,
"SchemaCompilerAnnotationWhenArraySizeEqual");
return result;
} else if (std::holds_alternative<SchemaCompilerAnnotationToParent>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAnnotationToParent");
const auto &annotation{std::get<SchemaCompilerAnnotationToParent>(step)};
Expand Down
20 changes: 15 additions & 5 deletions src/jsonschema/compile_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,26 +102,34 @@ auto value_to_json(const T &value) -> sourcemeta::jsontoolkit::JSON {
map.assign(string, std::move(dependencies));
}

result.assign("map", std::move(map));
result.assign("value", std::move(map));
return result;
} else if constexpr (std::is_same_v<
SchemaCompilerValueItemsAnnotationKeywords, T>) {
result.assign("type", JSON{"items-annotation-keywords"});
JSON values{JSON::make_object()};
result.assign("index", JSON{value.index});
JSON data{JSON::make_object()};
data.assign("index", JSON{value.index});

JSON mask{JSON::make_array()};
for (const auto &keyword : value.mask) {
mask.push_back(JSON{keyword});
}
result.assign("mask", std::move(mask));
data.assign("mask", std::move(mask));

JSON filter{JSON::make_array()};
for (const auto &keyword : value.filter) {
filter.push_back(JSON{keyword});
}
result.assign("filter", std::move(filter));
data.assign("filter", std::move(filter));

result.assign("value", std::move(data));
return result;
} else if constexpr (std::is_same_v<SchemaCompilerValueIndexedJSON, T>) {
result.assign("type", JSON{"indexed-json"});
JSON data{JSON::make_object()};
data.assign("index", JSON{value.first});
data.assign("value", value.second);
result.assign("value", std::move(data));
return result;
} else if constexpr (std::is_same_v<SchemaCompilerValueStringType, T>) {
result.assign("type", JSON{"string-type"});
Expand Down Expand Up @@ -211,6 +219,8 @@ struct StepVisitor {
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", "to-parent", SchemaCompilerAnnotationToParent)
HANDLE_STEP("annotation", "basename-to-parent",
SchemaCompilerAnnotationBasenameToParent)
Expand Down
9 changes: 4 additions & 5 deletions src/jsonschema/default_compiler_draft4.h
Original file line number Diff line number Diff line change
Expand Up @@ -629,11 +629,10 @@ auto compiler_draft4_applicator_items_array(
// The first entry
if (cursor == items_size) {
if (annotate) {
subchildren.push_back(make<SchemaCompilerAnnotationEmit>(
true, context, schema_context, relative_dynamic_context, JSON{true},
{make<SchemaCompilerAssertionSizeEqual>(
false, context, schema_context, relative_dynamic_context,
SchemaCompilerValueUnsignedInteger{cursor}, {})}));
subchildren.push_back(make<SchemaCompilerAnnotationWhenArraySizeEqual>(
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},
Expand Down
18 changes: 15 additions & 3 deletions src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ struct SchemaCompilerValueItemsAnnotationKeywords {
using SchemaCompilerValueStringMap =
std::map<SchemaCompilerValueString, SchemaCompilerValueStrings>;

/// @ingroup jsonschema
/// Represents a compiler step JSON value accompanied with an index
using SchemaCompilerValueIndexedJSON =
std::pair<SchemaCompilerValueUnsignedInteger, JSON>;

/// @ingroup jsonschema
/// Represents a compiler assertion step that always fails
struct SchemaCompilerAssertionFail;
Expand Down Expand Up @@ -207,6 +212,11 @@ struct SchemaCompilerAssertionSizeEqual;
/// Represents a compiler step that emits an annotation
struct SchemaCompilerAnnotationEmit;

/// @ingroup jsonschema
/// Represents a compiler step that emits an annotation when the size of the
/// array instance is equal to the given size
struct SchemaCompilerAnnotationWhenArraySizeEqual;

/// @ingroup jsonschema
/// Represents a compiler step that emits an annotation to the parent
struct SchemaCompilerAnnotationToParent;
Expand Down Expand Up @@ -345,9 +355,9 @@ using SchemaCompilerTemplate = std::vector<std::variant<
SchemaCompilerAssertionGreater, SchemaCompilerAssertionLess,
SchemaCompilerAssertionUnique, SchemaCompilerAssertionDivisible,
SchemaCompilerAssertionStringType, SchemaCompilerAssertionSizeEqual,
SchemaCompilerAnnotationEmit, SchemaCompilerAnnotationToParent,
SchemaCompilerAnnotationBasenameToParent, SchemaCompilerLogicalOr,
SchemaCompilerLogicalAnd, SchemaCompilerLogicalXor,
SchemaCompilerAnnotationEmit, SchemaCompilerAnnotationWhenArraySizeEqual,
SchemaCompilerAnnotationToParent, SchemaCompilerAnnotationBasenameToParent,
SchemaCompilerLogicalOr, SchemaCompilerLogicalAnd, SchemaCompilerLogicalXor,
SchemaCompilerLogicalTryMark, SchemaCompilerLogicalNot,
SchemaCompilerLogicalWhenType, SchemaCompilerLogicalWhenDefines,
SchemaCompilerLogicalWhenAdjacentUnmarked,
Expand Down Expand Up @@ -412,6 +422,8 @@ 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, 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 @@ -276,6 +276,12 @@
std::get<3>(trace_pre.at(index)))) { \
EVALUATE_TRACE_PRE(index, AnnotationToParent, evaluate_path, \
keyword_location, instance_location); \
} else if (std::holds_alternative< \
sourcemeta::jsontoolkit:: \
SchemaCompilerAnnotationWhenArraySizeEqual>( \
std::get<3>(trace_pre.at(index)))) { \
EVALUATE_TRACE_PRE(index, AnnotationWhenArraySizeEqual, evaluate_path, \
keyword_location, instance_location); \
} else { \
EVALUATE_TRACE_PRE(index, AnnotationEmit, evaluate_path, keyword_location, \
instance_location); \
Expand All @@ -296,6 +302,12 @@
std::get<3>(trace_post.at(index)))) { \
EVALUATE_TRACE_POST(index, AnnotationToParent, evaluate_path, \
keyword_location, instance_location); \
} else if (std::holds_alternative< \
sourcemeta::jsontoolkit:: \
SchemaCompilerAnnotationWhenArraySizeEqual>( \
std::get<3>(trace_post.at(index)))) { \
EVALUATE_TRACE_POST(index, AnnotationWhenArraySizeEqual, evaluate_path, \
keyword_location, instance_location); \
} else { \
EVALUATE_TRACE_POST(index, AnnotationEmit, evaluate_path, \
keyword_location, instance_location); \
Expand Down

4 comments on commit 446c8a5

@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: 446c8a5 Previous: 861acee Ratio
JSONSchema_Compile_Basic 201976.72209500815 ns/iter 203190.0137504512 ns/iter 0.99
JSONSchema_Validate_Draft4_Meta_1_No_Callback 1604.4579549939835 ns/iter 1732.8319284748634 ns/iter 0.93
JSONSchema_Validate_Draft4_Required_Properties 2475.1339894153225 ns/iter 2566.362104863072 ns/iter 0.96
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 167.52181478113985 ns/iter 177.15075221709478 ns/iter 0.95
JSONSchema_Validate_Draft4_Items_Schema 9575.568985114855 ns/iter 10243.713791583232 ns/iter 0.93

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: 446c8a5 Previous: 861acee Ratio
JSONSchema_Compile_Basic 384167.9646409036 ns/iter 391156.4874141713 ns/iter 0.98
JSONSchema_Validate_Draft4_Meta_1_No_Callback 16835.97709303747 ns/iter 2201.523071988668 ns/iter 7.65
JSONSchema_Validate_Draft4_Required_Properties 7743.500701434632 ns/iter 2576.5288447726366 ns/iter 3.01
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 1560.3980533702218 ns/iter 203.05273868883566 ns/iter 7.68
JSONSchema_Validate_Draft4_Items_Schema 106137.31248114795 ns/iter 10202.454392927002 ns/iter 10.40

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: 446c8a5 Previous: 861acee Ratio
JSONSchema_Validate_Draft4_Meta_1_No_Callback 2220.5265662275265 ns/iter 2153.7857272657093 ns/iter 1.03
JSONSchema_Validate_Draft4_Required_Properties 2808.450689032594 ns/iter 2876.647774572989 ns/iter 0.98
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 204.48548949175242 ns/iter 203.64644448456514 ns/iter 1.00
JSONSchema_Validate_Draft4_Items_Schema 11860.530190857715 ns/iter 11705.211405453107 ns/iter 1.01
JSONSchema_Compile_Basic 395414.15346258995 ns/iter 386147.24035282375 ns/iter 1.02

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: 446c8a5 Previous: 861acee Ratio
JSONSchema_Compile_Basic 775357.0312501219 ns/iter 799430.4687500682 ns/iter 0.97
JSONSchema_Validate_Draft4_Meta_1_No_Callback 4802.316969457869 ns/iter 4635.388025419744 ns/iter 1.04
JSONSchema_Validate_Draft4_Required_Properties 3685.3861607141375 ns/iter 3891.1559086501693 ns/iter 0.95
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 810.2354910715094 ns/iter 794.162500000084 ns/iter 1.02
JSONSchema_Validate_Draft4_Items_Schema 26093.417857144428 ns/iter 25196.089285714155 ns/iter 1.04

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

Please sign in to comment.