From 536d5b0f10b0000670ef1c8ed2684490a767ef85 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Mon, 23 Sep 2024 14:47:28 -0400 Subject: [PATCH] Introduce a new `LoopPropertiesType(Strict)` compiler step Signed-off-by: Juan Cruz Viotti --- src/jsonschema/compile_describe.cc | 16 +++ src/jsonschema/compile_evaluate.cc | 36 ++++++ src/jsonschema/compile_helpers.h | 4 +- src/jsonschema/compile_json.cc | 3 + src/jsonschema/default_compiler_draft4.h | 18 +++ .../jsontoolkit/jsonschema_compile.h | 15 ++- .../jsonschema_compile_draft4_test.cc | 106 ++++-------------- 7 files changed, 109 insertions(+), 89 deletions(-) diff --git a/src/jsonschema/compile_describe.cc b/src/jsonschema/compile_describe.cc index 8fdef5756..9a8b9886a 100644 --- a/src/jsonschema/compile_describe.cc +++ b/src/jsonschema/compile_describe.cc @@ -637,6 +637,22 @@ struct DescribeVisitor { return message.str(); } + auto operator()(const SchemaCompilerLoopPropertiesType &step) const + -> std::string { + std::ostringstream message; + message << "The object properties were expected to be of type " + << to_string(step.value); + return message.str(); + } + + auto operator()(const SchemaCompilerLoopPropertiesTypeStrict &step) const + -> std::string { + std::ostringstream message; + message << "The object properties were expected to be of type " + << to_string(step.value); + return message.str(); + } + auto operator()(const SchemaCompilerLoopKeys &) const -> std::string { assert(this->keyword == "propertyNames"); assert(this->target.is_object()); diff --git a/src/jsonschema/compile_evaluate.cc b/src/jsonschema/compile_evaluate.cc index f14ba4a92..06e149bf2 100644 --- a/src/jsonschema/compile_evaluate.cc +++ b/src/jsonschema/compile_evaluate.cc @@ -1005,6 +1005,42 @@ auto evaluate_step( evaluate_loop_properties_except_end: EVALUATE_END(loop, SchemaCompilerLoopPropertiesExcept); + } else if (IS_STEP(SchemaCompilerLoopPropertiesType)) { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesType, target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + const auto &entry_target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + if (entry_target.type() != loop.value && + (loop.value != JSON::Type::Integer || + entry_target.is_integer_real())) { + result = false; + context.leave(); + break; + } + + context.leave(); + } + + EVALUATE_END(loop, SchemaCompilerLoopPropertiesType); + } else if (IS_STEP(SchemaCompilerLoopPropertiesTypeStrict)) { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesTypeStrict, + target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + if (context.resolve_target().type() != loop.value) { + result = false; + context.leave(); + break; + } + + context.leave(); + } + + EVALUATE_END(loop, SchemaCompilerLoopPropertiesTypeStrict); } else if (IS_STEP(SchemaCompilerLoopKeys)) { EVALUATE_BEGIN(loop, SchemaCompilerLoopKeys, target.is_object()); result = true; diff --git a/src/jsonschema/compile_helpers.h b/src/jsonschema/compile_helpers.h index 89e15b90b..78e612505 100644 --- a/src/jsonschema/compile_helpers.h +++ b/src/jsonschema/compile_helpers.h @@ -17,7 +17,7 @@ auto make(const bool report, const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context, // Take the value type from the "type" property of the step struct - decltype(std::declval().value) &&value) -> Step { + const decltype(std::declval().value) &value) -> Step { return { dynamic_context.keyword.empty() ? dynamic_context.base_schema_location @@ -28,7 +28,7 @@ auto make(const bool report, const SchemaCompilerContext &context, schema_context.base.recompose(), context.uses_dynamic_scopes, report, - std::move(value)}; + value}; } // Instantiate an applicator step diff --git a/src/jsonschema/compile_json.cc b/src/jsonschema/compile_json.cc index cd6d7238f..61ba49a94 100644 --- a/src/jsonschema/compile_json.cc +++ b/src/jsonschema/compile_json.cc @@ -271,6 +271,9 @@ struct StepVisitor { HANDLE_STEP("loop", "properties-no-annotation", SchemaCompilerLoopPropertiesNoAnnotation) HANDLE_STEP("loop", "properties-except", SchemaCompilerLoopPropertiesExcept) + HANDLE_STEP("loop", "properties-type", SchemaCompilerLoopPropertiesType) + HANDLE_STEP("loop", "properties-type-strict", + SchemaCompilerLoopPropertiesTypeStrict) HANDLE_STEP("loop", "keys", SchemaCompilerLoopKeys) HANDLE_STEP("loop", "items", SchemaCompilerLoopItems) HANDLE_STEP("loop", "items-unmarked", SchemaCompilerLoopItemsUnmarked) diff --git a/src/jsonschema/default_compiler_draft4.h b/src/jsonschema/default_compiler_draft4.h index ebf7e49e1..8e4afd18e 100644 --- a/src/jsonschema/default_compiler_draft4.h +++ b/src/jsonschema/default_compiler_draft4.h @@ -613,6 +613,24 @@ auto compiler_draft4_applicator_additionalproperties_conditional_annotation( true, context, schema_context, dynamic_context, std::move(filter), std::move(children))}; } else { + if (children.size() == 1) { + // Optimize `additionalProperties` set to just `type`, which is a + // pretty common pattern + if (std::holds_alternative( + children.front())) { + const auto &type_step{ + std::get(children.front())}; + return {make( + true, context, schema_context, dynamic_context, type_step.value)}; + } else if (std::holds_alternative( + children.front())) { + const auto &type_step{ + std::get(children.front())}; + return {make( + true, context, schema_context, dynamic_context, type_step.value)}; + } + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueNone{}, std::move(children))}; diff --git a/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h b/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h index 5d50892c9..964ac936d 100644 --- a/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h +++ b/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h @@ -171,6 +171,8 @@ struct SchemaCompilerLoopProperties; struct SchemaCompilerLoopPropertiesRegex; struct SchemaCompilerLoopPropertiesNoAnnotation; struct SchemaCompilerLoopPropertiesExcept; +struct SchemaCompilerLoopPropertiesType; +struct SchemaCompilerLoopPropertiesTypeStrict; struct SchemaCompilerLoopKeys; struct SchemaCompilerLoopItems; struct SchemaCompilerLoopItemsUnmarked; @@ -214,7 +216,8 @@ using SchemaCompilerTemplate = std::vector