From b29de65eba42300f53c975cbbdb763d426492661 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Mon, 2 Sep 2024 18:06:21 -0400 Subject: [PATCH] Implement a new `AnnotationWhenArraySizeGreater` compiler step (#1098) Signed-off-by: Juan Cruz Viotti --- src/jsonschema/compile_describe.cc | 28 ++++++++++++--- src/jsonschema/compile_evaluate.cc | 36 +++++++++++-------- src/jsonschema/compile_json.cc | 3 +- src/jsonschema/default_compiler_draft4.h | 11 +++--- .../jsontoolkit/jsonschema_compile.h | 19 +++++----- test/jsonschema/jsonschema_test_utils.h | 12 +++++++ 6 files changed, 73 insertions(+), 36 deletions(-) diff --git a/src/jsonschema/compile_describe.cc b/src/jsonschema/compile_describe.cc index ef5101573..eecd9eb12 100644 --- a/src/jsonschema/compile_describe.cc +++ b/src/jsonschema/compile_describe.cc @@ -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() && @@ -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(); } diff --git a/src/jsonschema/compile_evaluate.cc b/src/jsonschema/compile_evaluate.cc index ee628e40d..68022a0ff 100644 --- a/src/jsonschema/compile_evaluate.cc +++ b/src/jsonschema/compile_evaluate.cc @@ -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(step)) { - SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionSizeEqual"); - const auto &assertion{std::get(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(step)) { SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionEqual"); const auto &assertion{std::get(step)}; @@ -909,9 +897,6 @@ auto evaluate_step( SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAnnotationEmit"); const auto &annotation{std::get(step)}; context.push(annotation); - // TODO: Get rid of this - EVALUATE_CONDITION_GUARD("SchemaCompilerAnnotationEmit", annotation, - instance); // Annotations never fail result = true; const auto ¤t_instance_location{context.instance_location()}; @@ -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(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 ¤t_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(step)) { SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAnnotationToParent"); const auto &annotation{std::get(step)}; diff --git a/src/jsonschema/compile_json.cc b/src/jsonschema/compile_json.cc index 75234480c..5e08b513a 100644 --- a/src/jsonschema/compile_json.cc +++ b/src/jsonschema/compile_json.cc @@ -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) diff --git a/src/jsonschema/default_compiler_draft4.h b/src/jsonschema/default_compiler_draft4.h index 948947dbe..d68a42ae1 100644 --- a/src/jsonschema/default_compiler_draft4.h +++ b/src/jsonschema/default_compiler_draft4.h @@ -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( - true, context, schema_context, relative_dynamic_context, - JSON{cursor - 1}, - {make( + subchildren.push_back( + make( true, context, schema_context, relative_dynamic_context, - SchemaCompilerValueUnsignedInteger{cursor}, {})})); + SchemaCompilerValueIndexedJSON{cursor, JSON{cursor - 1}}, + SchemaCompilerTemplate{})); } children.push_back(make( @@ -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( true, context, schema_context, dynamic_context, JSON::Type::Array, std::move(children), SchemaCompilerTemplate{})}; diff --git a/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h b/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h index edfa1a815..b7c1a7bba 100644 --- a/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h +++ b/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h @@ -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; @@ -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; @@ -354,8 +353,9 @@ using SchemaCompilerTemplate = std::vector(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); \ @@ -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); \