From e1fbd4bb0ee98abd94135d861455007b60a3ec11 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 10 Oct 2024 17:34:24 -0400 Subject: [PATCH] Only apply type bounds optimizations on `FastValidation` mode (#1280) Signed-off-by: Juan Cruz Viotti --- src/jsonschema/default_compiler_draft4.h | 27 ++-- test/evaluator/evaluator_draft4_test.cc | 163 +++++++++++++++++++++++ 2 files changed, 181 insertions(+), 9 deletions(-) diff --git a/src/jsonschema/default_compiler_draft4.h b/src/jsonschema/default_compiler_draft4.h index 2ea556010..ba459bfda 100644 --- a/src/jsonschema/default_compiler_draft4.h +++ b/src/jsonschema/default_compiler_draft4.h @@ -134,7 +134,8 @@ auto compiler_draft4_validation_type( unsigned_integer_property(schema_context.schema, "minProperties", 0)}; const auto maximum{ unsigned_integer_property(schema_context.schema, "maxProperties")}; - if (minimum > 0 || maximum.has_value()) { + if (context.mode == SchemaCompilerMode::FastValidation && + (minimum > 0 || maximum.has_value())) { return {make( true, context, schema_context, dynamic_context, {minimum, maximum, false})}; @@ -147,7 +148,8 @@ auto compiler_draft4_validation_type( unsigned_integer_property(schema_context.schema, "minItems", 0)}; const auto maximum{ unsigned_integer_property(schema_context.schema, "maxItems")}; - if (minimum > 0 || maximum.has_value()) { + if (context.mode == SchemaCompilerMode::FastValidation && + (minimum > 0 || maximum.has_value())) { return {make( true, context, schema_context, dynamic_context, {minimum, maximum, false})}; @@ -167,7 +169,8 @@ auto compiler_draft4_validation_type( unsigned_integer_property(schema_context.schema, "minLength", 0)}; const auto maximum{ unsigned_integer_property(schema_context.schema, "maxLength")}; - if (minimum > 0 || maximum.has_value()) { + if (context.mode == SchemaCompilerMode::FastValidation && + (minimum > 0 || maximum.has_value())) { return {make( true, context, schema_context, dynamic_context, {minimum, maximum, false})}; @@ -1123,7 +1126,8 @@ auto compiler_draft4_validation_maxlength( } // We'll handle it at the type level as an optimization - if (schema_context.schema.defines("type") && + if (context.mode == SchemaCompilerMode::FastValidation && + schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() == "string") { return {}; @@ -1153,7 +1157,8 @@ auto compiler_draft4_validation_minlength( } // We'll handle it at the type level as an optimization - if (schema_context.schema.defines("type") && + if (context.mode == SchemaCompilerMode::FastValidation && + schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() == "string") { return {}; @@ -1183,7 +1188,8 @@ auto compiler_draft4_validation_maxitems( } // We'll handle it at the type level as an optimization - if (schema_context.schema.defines("type") && + if (context.mode == SchemaCompilerMode::FastValidation && + schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() == "array") { return {}; @@ -1213,7 +1219,8 @@ auto compiler_draft4_validation_minitems( } // We'll handle it at the type level as an optimization - if (schema_context.schema.defines("type") && + if (context.mode == SchemaCompilerMode::FastValidation && + schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() == "array") { return {}; @@ -1243,7 +1250,8 @@ auto compiler_draft4_validation_maxproperties( } // We'll handle it at the type level as an optimization - if (schema_context.schema.defines("type") && + if (context.mode == SchemaCompilerMode::FastValidation && + schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() == "object") { return {}; @@ -1273,7 +1281,8 @@ auto compiler_draft4_validation_minproperties( } // We'll handle it at the type level as an optimization - if (schema_context.schema.defines("type") && + if (context.mode == SchemaCompilerMode::FastValidation && + schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() == "object") { return {}; diff --git a/test/evaluator/evaluator_draft4_test.cc b/test/evaluator/evaluator_draft4_test.cc index f93ac37e7..4c1defb5f 100644 --- a/test/evaluator/evaluator_draft4_test.cc +++ b/test/evaluator/evaluator_draft4_test.cc @@ -3283,6 +3283,33 @@ TEST(JSONSchema_evaluator_draft4, minLength_4) { "The value was expected to consist of a string of at least 1 character"); } +TEST(JSONSchema_evaluator_draft4, minLength_4_exhaustive) { + const sourcemeta::jsontoolkit::JSON schema{ + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "string", + "minLength": 1 + })JSON")}; + + const sourcemeta::jsontoolkit::JSON instance{"xx"}; + EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 2); + + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_PRE(1, AssertionStringSizeGreater, "/minLength", "#/minLength", + ""); + + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_POST_SUCCESS(1, AssertionStringSizeGreater, "/minLength", + "#/minLength", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type string"); + EVALUATE_TRACE_POST_DESCRIBE( + instance, 1, + "The string value \"xx\" was expected to consist of at least 1 character " + "and it consisted of 2 characters"); +} + TEST(JSONSchema_evaluator_draft4, minLength_5) { const sourcemeta::jsontoolkit::JSON schema{ sourcemeta::jsontoolkit::parse(R"JSON({ @@ -3372,6 +3399,33 @@ TEST(JSONSchema_evaluator_draft4, maxLength_4) { "The value was expected to consist of a string of at most 2 characters"); } +TEST(JSONSchema_evaluator_draft4, maxLength_4_exhaustive) { + const sourcemeta::jsontoolkit::JSON schema{ + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "string", + "maxLength": 2 + })JSON")}; + + const sourcemeta::jsontoolkit::JSON instance{"xx"}; + EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 2); + + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_PRE(1, AssertionStringSizeLess, "/maxLength", "#/maxLength", + ""); + + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_POST_SUCCESS(1, AssertionStringSizeLess, "/maxLength", + "#/maxLength", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type string"); + EVALUATE_TRACE_POST_DESCRIBE( + instance, 1, + "The string value \"xx\" was expected to consist of at most 2 characters " + "and it consisted of 2 characters"); +} + TEST(JSONSchema_evaluator_draft4, maxLength_5) { const sourcemeta::jsontoolkit::JSON schema{ sourcemeta::jsontoolkit::parse(R"JSON({ @@ -3504,6 +3558,33 @@ TEST(JSONSchema_evaluator_draft4, minItems_5) { "The value was expected to consist of an array of at least 2 items"); } +TEST(JSONSchema_evaluator_draft4, minItems_5_exhaustive) { + const sourcemeta::jsontoolkit::JSON schema{ + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "minItems": 2 + })JSON")}; + + const sourcemeta::jsontoolkit::JSON instance{ + sourcemeta::jsontoolkit::parse("[ 1, 2, 3 ]")}; + EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 2); + + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_PRE(1, AssertionArraySizeGreater, "/minItems", "#/minItems", + ""); + + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_POST_SUCCESS(1, AssertionArraySizeGreater, "/minItems", + "#/minItems", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type array"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The array value was expected to contain at " + "least 2 items and it contained 3 items"); +} + TEST(JSONSchema_evaluator_draft4, minItems_6) { const sourcemeta::jsontoolkit::JSON schema{ sourcemeta::jsontoolkit::parse(R"JSON({ @@ -3614,6 +3695,32 @@ TEST(JSONSchema_evaluator_draft4, maxItems_5) { "The value was expected to consist of an array of at most 2 items"); } +TEST(JSONSchema_evaluator_draft4, maxItems_5_exhaustive) { + const sourcemeta::jsontoolkit::JSON schema{ + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "maxItems": 2 + })JSON")}; + + const sourcemeta::jsontoolkit::JSON instance{ + sourcemeta::jsontoolkit::parse("[ 1, 2 ]")}; + EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 2); + + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_PRE(1, AssertionArraySizeLess, "/maxItems", "#/maxItems", ""); + + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_POST_SUCCESS(1, AssertionArraySizeLess, "/maxItems", + "#/maxItems", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type array"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The array value was expected to contain at " + "most 2 items and it contained 2 items"); +} + TEST(JSONSchema_evaluator_draft4, maxItems_6) { const sourcemeta::jsontoolkit::JSON schema{ sourcemeta::jsontoolkit::parse(R"JSON({ @@ -3732,6 +3839,34 @@ TEST(JSONSchema_evaluator_draft4, minProperties_4) { "The value was expected to consist of an object of at least 1 property"); } +TEST(JSONSchema_evaluator_draft4, minProperties_4_exhaustive) { + const sourcemeta::jsontoolkit::JSON schema{ + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "minProperties": 1 + })JSON")}; + + const sourcemeta::jsontoolkit::JSON instance{ + sourcemeta::jsontoolkit::parse("{ \"foo\": 1 }")}; + EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 2); + + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectSizeGreater, "/minProperties", + "#/minProperties", ""); + + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_POST_SUCCESS(1, AssertionObjectSizeGreater, "/minProperties", + "#/minProperties", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type object"); + EVALUATE_TRACE_POST_DESCRIBE( + instance, 1, + "The object value was expected to contain at least 1 property and it " + "contained 1 property: \"foo\""); +} + TEST(JSONSchema_evaluator_draft4, minProperties_5) { const sourcemeta::jsontoolkit::JSON schema{ sourcemeta::jsontoolkit::parse(R"JSON({ @@ -3826,6 +3961,34 @@ TEST(JSONSchema_evaluator_draft4, maxProperties_4) { "The value was expected to consist of an object of at most 2 properties"); } +TEST(JSONSchema_evaluator_draft4, maxProperties_4_exhaustive) { + const sourcemeta::jsontoolkit::JSON schema{ + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "maxProperties": 2 + })JSON")}; + + const sourcemeta::jsontoolkit::JSON instance{ + sourcemeta::jsontoolkit::parse("{ \"bar\": 2, \"foo\": 1 }")}; + EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 2); + + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectSizeLess, "/maxProperties", + "#/maxProperties", ""); + + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_POST_SUCCESS(1, AssertionObjectSizeLess, "/maxProperties", + "#/maxProperties", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type object"); + EVALUATE_TRACE_POST_DESCRIBE( + instance, 1, + "The object value was expected to contain at most 2 properties and it " + "contained 2 properties: \"bar\", and \"foo\""); +} + TEST(JSONSchema_evaluator_draft4, maxProperties_5) { const sourcemeta::jsontoolkit::JSON schema{ sourcemeta::jsontoolkit::parse(R"JSON({