diff --git a/src/compiler/compile.cc b/src/compiler/compile.cc index 7cf3354a0..ec1c1277b 100644 --- a/src/compiler/compile.cc +++ b/src/compiler/compile.cc @@ -14,6 +14,12 @@ #include // std::move, std::pair #include // std::vector +#if defined(SOURCEMETA_BLAZE_DEBUG_TRACK_FLAGS) +#include // stderr +#include // std::function +#include // std::println +#endif + #include "compile_helpers.h" #include "postprocess.h" @@ -148,6 +154,138 @@ auto schema_frame_populate_target_types( } } +auto is_evaluation_mark_instruction( + const sourcemeta::blaze::InstructionIndex type) -> bool { + using sourcemeta::blaze::InstructionIndex; + switch (type) { + case InstructionIndex::AssertionArrayPrefixEvaluate: + case InstructionIndex::AssertionPropertyTypeEvaluate: + case InstructionIndex::AssertionPropertyTypeStrictEvaluate: + case InstructionIndex::AssertionPropertyTypeStrictAnyEvaluate: + case InstructionIndex::Evaluate: + case InstructionIndex::LogicalNotEvaluate: + case InstructionIndex::LoopPropertiesUnevaluated: + case InstructionIndex::LoopPropertiesUnevaluatedExcept: + case InstructionIndex::LoopPropertiesEvaluate: + case InstructionIndex::LoopPropertiesTypeEvaluate: + case InstructionIndex::LoopPropertiesTypeStrictEvaluate: + case InstructionIndex::LoopPropertiesTypeStrictAnyEvaluate: + case InstructionIndex::LoopItemsUnevaluated: + case InstructionIndex::ControlEvaluate: + return true; + default: + return false; + } +} + +auto contains_evaluation_marks(const sourcemeta::blaze::Instructions &steps, + const std::vector &target_marks, + const bool dynamic_marks) -> bool { + using sourcemeta::blaze::InstructionIndex; + for (const auto &step : steps) { + if (is_evaluation_mark_instruction(step.type)) { + return true; + } + + if (step.type == InstructionIndex::ControlJump && + target_marks[std::get( + step.value)]) { + return true; + } + + if (step.type == InstructionIndex::ControlDynamicAnchorJump && + dynamic_marks) { + return true; + } + + if (contains_evaluation_marks(step.children, target_marks, dynamic_marks)) { + return true; + } + } + + return false; +} + +auto flag_evaluation_marks(sourcemeta::blaze::Instructions &steps, + const std::vector &target_marks, + const bool dynamic_marks) -> bool { + using sourcemeta::blaze::InstructionIndex; + bool result{false}; + for (auto &step : steps) { + step.track = + is_evaluation_mark_instruction(step.type) || + (step.type == InstructionIndex::ControlJump && + target_marks[std::get( + step.value)]) || + (step.type == InstructionIndex::ControlDynamicAnchorJump && + dynamic_marks); + if (flag_evaluation_marks(step.children, target_marks, dynamic_marks)) { + step.track = true; + } + + result = result || step.track; + } + + return result; +} + +// Determine which instructions can produce or consume evaluation marks +// somewhere in their subtree, including through static and dynamic jumps, +// so that the evaluator only maintains the evaluate path on the spines that +// lead to those instructions. Note that the evaluate path of every mark and +// of every mark consumer is preserved exactly: all of their ancestors are +// flagged, and only instructions that no mark or consumer can ever observe +// stop pushing +auto flag_evaluation_tracking( + std::vector &targets, + const std::vector> &labels) -> void { + std::vector target_marks(targets.size(), false); + bool dynamic_marks{false}; + bool changed{true}; + while (changed) { + changed = false; + for (std::size_t index = 0; index < targets.size(); index++) { + if (!target_marks[index] && + contains_evaluation_marks(targets[index], target_marks, + dynamic_marks)) { + target_marks[index] = true; + changed = true; + } + } + + if (!dynamic_marks && + std::ranges::any_of(labels, [&target_marks](const auto &label) { + return target_marks[label.second]; + })) { + dynamic_marks = true; + changed = true; + } + } + + for (auto &target : targets) { + flag_evaluation_marks(target, target_marks, dynamic_marks); + } + +#if defined(SOURCEMETA_BLAZE_DEBUG_TRACK_FLAGS) + std::size_t flagged{0}; + std::size_t total{0}; + // NOLINTNEXTLINE(misc-no-recursion) + const std::function count{ + [&flagged, &total, &count](const sourcemeta::blaze::Instructions &steps) { + for (const auto &step : steps) { + total += 1; + flagged += step.track ? 1 : 0; + count(step.children); + } + }}; + for (const auto &target : targets) { + count(target); + } + + std::println(stderr, "TRACK FLAGS: {} / {}", flagged, total); +#endif +} + } // namespace namespace sourcemeta::blaze { @@ -421,6 +559,11 @@ auto compile(const sourcemeta::core::JSON &schema, std::ranges::any_of(context.unevaluated, [](const auto &dependency) { return dependency.first.ends_with("unevaluatedItems"); })}; + + if (track) { + flag_evaluation_tracking(compiled_targets, labels_map); + } + return {.dynamic = uses_dynamic_scopes, .track = track, .targets = std::move(compiled_targets), diff --git a/src/compiler/default_compiler_draft3.h b/src/compiler/default_compiler_draft3.h index 198d2d7ae..6ce039e2d 100644 --- a/src/compiler/default_compiler_draft3.h +++ b/src/compiler/default_compiler_draft3.h @@ -8,8 +8,11 @@ #include // std::sort, std::ranges::any_of, std::ranges::all_of, std::find_if, std::ranges::none_of #include // assert +#include // std::map #include // std::set -#include // std::move, std::to_underlying +#include // std::string +#include // std::unordered_map +#include // std::move, std::to_underlying #include "compile_helpers.h" @@ -104,10 +107,17 @@ compile_properties(const sourcemeta::blaze::Context &context, // we prefer to evaluate smaller subschemas first, in the hope of failing // earlier without spending a lot of time on other subschemas if (context.tweaks.properties_reorder) { - std::ranges::sort(properties, [&context](const auto &left, - const auto &right) { - const auto left_size{recursive_template_size(left.second)}; - const auto right_size{recursive_template_size(right.second)}; + std::unordered_map template_sizes; + template_sizes.reserve(properties.size()); + for (const auto &property : properties) { + template_sizes.emplace(property.first, + recursive_template_size(property.second)); + } + + std::ranges::sort(properties, [&context, &template_sizes]( + const auto &left, const auto &right) { + const auto left_size{template_sizes.at(left.first)}; + const auto right_size{template_sizes.at(right.first)}; if (left_size == right_size) { const auto left_direct_enumeration{ defines_direct_enumeration(left.second)}; @@ -548,10 +558,11 @@ auto compiler_draft3_applicator_properties_with_options( ValueNamedIndexes indexes; Instructions children; std::size_t cursor = 0; + std::map child_positions; for (auto &&[name, substeps] : compile_properties( context, schema_context, relative_dynamic_context(), current)) { - indexes.emplace(name, cursor); + child_positions.emplace(name, cursor); if (track_evaluation) { substeps.push_back(make( @@ -574,6 +585,17 @@ auto compiler_draft3_applicator_properties_with_options( cursor += 1; } + // Lay the lookup table out in schema declaration order, as instances + // commonly follow it, which makes scanning the table for each instance + // property terminate sooner on average + for (const auto &entry : + schema_context.schema.at(dynamic_context.keyword).as_object()) { + const auto match{child_positions.find(entry.first)}; + if (match != child_positions.cend()) { + indexes.emplace(entry.first, match->second); + } + } + if (context.mode == Mode::FastValidation && !track_evaluation && !schema_context.schema.defines("patternProperties") && schema_context.schema.defines("additionalProperties") && @@ -892,6 +914,47 @@ auto compiler_draft3_applicator_properties_with_options( for (auto &&step : substeps) { children.push_back(std::move(step)); } + } else if ( + context.mode == Mode::FastValidation && + std::ranges::all_of(substeps, [&name](const auto &step) { + if (step.relative_instance_location.empty() || + !step.relative_instance_location.at(0).is_property() || + step.relative_instance_location.at(0).to_property() != + name) { + return false; + } + + if (step.relative_instance_location.size() > 1) { + return true; + } + + // These instructions require a non-empty + // instance location of their own + switch (step.type) { + case InstructionIndex::AssertionPropertyType: + case InstructionIndex::AssertionPropertyTypeEvaluate: + case InstructionIndex::AssertionPropertyTypeStrict: + case InstructionIndex::AssertionPropertyTypeStrictEvaluate: + case InstructionIndex::AssertionPropertyTypeStrictAny: + case InstructionIndex::AssertionPropertyTypeStrictAnyEvaluate: + case InstructionIndex::ControlGroupWhenDefines: + return false; + default: + return true; + } + })) { + // When every step navigates into the property, we can resolve + // it once and evaluate the steps against its value directly + for (auto &step : substeps) { + step.relative_instance_location = + step.relative_instance_location.slice(1); + } + + children.push_back(make(sourcemeta::blaze::InstructionIndex:: + ControlGroupWhenDefinesResolved, + context, schema_context, + effective_dynamic_context, + make_property(name), std::move(substeps))); } else { children.push_back(make(sourcemeta::blaze::InstructionIndex:: ControlGroupWhenDefinesDirect, diff --git a/src/compiler/default_compiler_draft4.h b/src/compiler/default_compiler_draft4.h index 08f9a5517..a3658fbcc 100644 --- a/src/compiler/default_compiler_draft4.h +++ b/src/compiler/default_compiler_draft4.h @@ -5,9 +5,14 @@ #include #include // std::ranges::any_of, std::ranges::all_of, std::ranges::none_of, std::find_if -#include // assert -#include // std::set -#include // std::move, std::to_underlying +#include // assert +#include // std::map +#include // std::optional, std::nullopt +#include // std::set +#include // std::string_view +#include // std::unordered_set +#include // std::move, std::to_underlying +#include // std::vector #include "compile_helpers.h" #include "default_compiler_draft3.h" @@ -73,6 +78,264 @@ auto compiler_draft4_applicator_allof(const Context &context, } } +// The set of facts about a disjunct that hold no matter what, used to +// statically prove that a disjunction is a discriminated union +struct SwitchDisjunctFacts { + bool object_only{false}; + bool never_object{false}; + std::set required; + std::map> + pinned; +}; + +auto is_known_non_object_type_name(const sourcemeta::core::JSON &entry) + -> bool { + if (!entry.is_string()) { + return false; + } + + const auto &name{entry.to_string()}; + return name == "null" || name == "boolean" || name == "array" || + name == "string" || name == "number" || name == "integer"; +} + +// Gather facts that necessarily hold for any instance that validates +// against the given subschema, following static references and `allOf` +// conjunctions. Note that when a subschema declares `$ref`, we ignore its +// siblings: older dialects consider them inert, and on newer dialects +// doing so only loses information, never inventing facts +auto collect_switch_disjunct_facts( + const Context &context, const sourcemeta::core::JSON &subschema, + const sourcemeta::blaze::SchemaFrame::Location &location, + const std::string_view root_dialect, const Vocabularies &root_vocabularies, + std::unordered_set + &visited, + SwitchDisjunctFacts &facts) -> void { + if (!subschema.is_object() || !visited.insert(&location).second) { + return; + } + + if (subschema.defines("$ref") && subschema.at("$ref").is_string()) { + static const sourcemeta::core::JSON::String ref_keyword{"$ref"}; + const auto reference{context.frame.reference( + sourcemeta::blaze::SchemaReferenceType::Static, + location.pointer.concat(make_weak_pointer(ref_keyword)))}; + if (reference.has_value()) { + const auto destination{ + context.frame.traverse(reference->get().destination)}; + if (destination.has_value()) { + collect_switch_disjunct_facts( + context, + sourcemeta::core::get(context.root, destination->get().pointer), + destination->get(), root_dialect, root_vocabularies, visited, + facts); + } + } + + return; + } + + using Known = sourcemeta::blaze::Vocabularies::Known; + const auto vocabularies{ + location.dialect == root_dialect + ? root_vocabularies + : context.frame.vocabularies(location, context.resolver)}; + const auto supports_validation{ + vocabularies.contains(Known::JSON_Schema_Draft_4) || + vocabularies.contains(Known::JSON_Schema_Draft_6) || + vocabularies.contains(Known::JSON_Schema_Draft_7) || + vocabularies.contains(Known::JSON_Schema_2019_09_Validation) || + vocabularies.contains(Known::JSON_Schema_2020_12_Validation)}; + const auto supports_applicator{ + vocabularies.contains(Known::JSON_Schema_Draft_4) || + vocabularies.contains(Known::JSON_Schema_Draft_6) || + vocabularies.contains(Known::JSON_Schema_Draft_7) || + vocabularies.contains(Known::JSON_Schema_2019_09_Applicator) || + vocabularies.contains(Known::JSON_Schema_2020_12_Applicator)}; + const auto supports_const{ + vocabularies.contains(Known::JSON_Schema_Draft_6) || + vocabularies.contains(Known::JSON_Schema_Draft_7) || + vocabularies.contains(Known::JSON_Schema_2019_09_Validation) || + vocabularies.contains(Known::JSON_Schema_2020_12_Validation)}; + + if (supports_validation) { + if (subschema.defines("type")) { + const auto &type{subschema.at("type")}; + if ((type.is_string() && type.to_string() == "object") || + (type.is_array() && !type.empty() && + std::ranges::all_of(type.as_array(), [](const auto &entry) { + return entry.is_string() && entry.to_string() == "object"; + }))) { + facts.object_only = true; + } else if (is_known_non_object_type_name(type) || + (type.is_array() && !type.empty() && + std::ranges::all_of(type.as_array(), + is_known_non_object_type_name))) { + facts.never_object = true; + } + } + + if (subschema.defines("enum") && subschema.at("enum").is_array() && + std::ranges::none_of( + subschema.at("enum").as_array(), + [](const auto &option) { return option.is_object(); })) { + facts.never_object = true; + } + + if (supports_const && subschema.defines("const") && + !subschema.at("const").is_object()) { + facts.never_object = true; + } + + if (subschema.defines("required") && subschema.at("required").is_array()) { + for (const auto &entry : subschema.at("required").as_array()) { + if (entry.is_string()) { + facts.required.insert(entry.to_string()); + } + } + } + } + + if (supports_applicator && subschema.defines("properties") && + subschema.at("properties").is_object()) { + for (const auto &entry : subschema.at("properties").as_object()) { + if (facts.pinned.contains(entry.first) || !entry.second.is_object()) { + continue; + } + + std::vector values; + if (supports_const && entry.second.defines("const") && + entry.second.at("const").is_string()) { + values.push_back(entry.second.at("const").to_string()); + } else if (supports_validation && entry.second.defines("enum") && + entry.second.at("enum").is_array() && + !entry.second.at("enum").empty() && + std::ranges::all_of( + entry.second.at("enum").as_array(), + [](const auto &option) { return option.is_string(); })) { + for (const auto &option : entry.second.at("enum").as_array()) { + values.push_back(option.to_string()); + } + } else { + continue; + } + + facts.pinned.emplace(entry.first, std::move(values)); + } + } + + if (supports_applicator && subschema.defines("allOf") && + subschema.at("allOf").is_array()) { + static const sourcemeta::core::JSON::String allof_keyword{"allOf"}; + for (std::size_t index = 0; index < subschema.at("allOf").size(); index++) { + collect_switch_disjunct_facts( + context, subschema.at("allOf").at(index), + context.frame.traverse(location, + make_weak_pointer(allof_keyword, index)), + root_dialect, root_vocabularies, visited, facts); + } + } +} + +// Attempt to compile a disjunction whose disjuncts all statically pin a +// shared discriminator property to disjoint constant strings (on top of +// requiring it on an object instance) into a single switch instruction +// that picks the only disjunct that can possibly match in constant time. +// Note that the given disjuncts are left untouched unless an instruction +// could indeed be derived +auto compile_switch_property_string(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + Instructions &disjunctors) + -> std::optional { + const auto &disjunction{schema_context.schema.at(dynamic_context.keyword)}; + if (disjunction.size() < 2) { + return std::nullopt; + } + + const auto &keyword_entry{static_frame_entry(context, schema_context)}; + std::vector facts_list; + facts_list.reserve(disjunction.size()); + // The index plus one of the only disjunct that can match non-object + // instances, where zero means no such disjunct exists + ValueUnsignedInteger otherwise{0}; + std::vector discriminated; + discriminated.reserve(disjunction.size()); + for (std::size_t index = 0; index < disjunction.size(); index++) { + sourcemeta::core::WeakPointer disjunct_suffix; + disjunct_suffix.push_back(index); + SwitchDisjunctFacts facts; + std::unordered_set + visited; + collect_switch_disjunct_facts( + context, disjunction.at(index), + context.frame.traverse(keyword_entry, disjunct_suffix), + keyword_entry.dialect, schema_context.vocabularies, visited, facts); + if (facts.never_object) { + if (otherwise > 0) { + return std::nullopt; + } + + otherwise = index + 1; + } else if (facts.object_only && !facts.pinned.empty()) { + discriminated.push_back(index); + } else { + return std::nullopt; + } + + facts_list.push_back(std::move(facts)); + } + + if (discriminated.size() < 2) { + return std::nullopt; + } + + for (const auto &candidate : facts_list[discriminated.front()].pinned) { + const auto &name{candidate.first}; + ValueNamedIndexes indexes; + bool valid{true}; + for (const auto index : discriminated) { + const auto &facts{facts_list[index]}; + if (!facts.required.contains(name)) { + valid = false; + break; + } + + const auto match{facts.pinned.find(name)}; + if (match == facts.pinned.cend()) { + valid = false; + break; + } + + for (const auto &option : match->second) { + const auto hash{indexes.hash(option)}; + if (indexes.defines(option, hash)) { + valid = false; + break; + } + + indexes.emplace_assume_new(option, ValueUnsignedInteger{index}, hash); + } + + if (!valid) { + break; + } + } + + if (valid) { + return make( + sourcemeta::blaze::InstructionIndex::LogicalSwitchPropertyString, + context, schema_context, dynamic_context, + ValuePropertySwitch{make_property(name), std::move(indexes), + otherwise}, + std::move(disjunctors)); + } + } + + return std::nullopt; +} + auto compiler_draft4_applicator_anyof(const Context &context, const SchemaContext &schema_context, const DynamicContext &dynamic_context, @@ -143,6 +406,15 @@ auto compiler_draft4_applicator_anyof(const Context &context, const auto requires_exhaustive{context.mode == Mode::Exhaustive || requires_evaluation(context, schema_context)}; + if (context.mode == Mode::FastValidation && !requires_exhaustive && + !schema_context.is_property_name) { + auto switched{compile_switch_property_string(context, schema_context, + dynamic_context, disjunctors)}; + if (switched.has_value()) { + return {std::move(switched).value()}; + } + } + return {make(sourcemeta::blaze::InstructionIndex::LogicalOr, context, schema_context, dynamic_context, ValueBoolean{requires_exhaustive}, std::move(disjunctors))}; @@ -173,6 +445,18 @@ auto compiler_draft4_applicator_oneof(const Context &context, const auto requires_exhaustive{context.mode == Mode::Exhaustive || requires_evaluation(context, schema_context)}; + // As the discriminator property values across disjuncts are proven to be + // disjoint, at most one disjunct can ever match, so the exclusivity that + // `oneOf` demands holds statically and the switch is also valid here + if (context.mode == Mode::FastValidation && !requires_exhaustive && + !schema_context.is_property_name) { + auto switched{compile_switch_property_string(context, schema_context, + dynamic_context, disjunctors)}; + if (switched.has_value()) { + return {std::move(switched).value()}; + } + } + return {make(sourcemeta::blaze::InstructionIndex::LogicalXor, context, schema_context, dynamic_context, ValueBoolean{requires_exhaustive}, std::move(disjunctors))}; diff --git a/src/compiler/postprocess.h b/src/compiler/postprocess.h index a959457ae..bb12a2340 100644 --- a/src/compiler/postprocess.h +++ b/src/compiler/postprocess.h @@ -9,6 +9,7 @@ #include // std::string #include #include // std::unordered_set +#include // std::to_underlying #include // TODO: Move all `FastValidation` conditional optimisations from the default @@ -43,6 +44,7 @@ inline auto is_noop_without_children(const InstructionIndex type) noexcept case InstructionIndex::LoopContains: case InstructionIndex::ControlGroupWhenDefines: case InstructionIndex::ControlGroupWhenDefinesDirect: + case InstructionIndex::ControlGroupWhenDefinesResolved: case InstructionIndex::ControlGroupWhenType: return true; default: @@ -139,6 +141,7 @@ inline auto collect_statistics(const Instructions &instructions, } if (instruction.type == InstructionIndex::ControlGroupWhenDefinesDirect || + instruction.type == InstructionIndex::ControlGroupWhenDefinesResolved || instruction.type == InstructionIndex::ControlGroupWhenType) { statistics.requires_empty_instance_location = true; } @@ -153,7 +156,8 @@ transform_instruction(Instruction &instruction, Instructions &output, const std::vector &targets, const std::vector &statistics, TargetStatistics ¤t_stats, const Tweaks &tweaks, - const bool uses_dynamic_scopes) -> bool { + const bool uses_dynamic_scopes, + const bool allow_adjacent_fusion) -> bool { if (instruction.type == InstructionIndex::ControlJump) { const auto jump_target_index{ std::get(instruction.value)}; @@ -327,6 +331,46 @@ transform_instruction(Instruction &instruction, Instructions &output, } } + // An array items type check followed by an array type plus bounds check + // on the same location (or vice versa) collapses into a single pass. This + // must not happen inside children vectors that parents index by position + if (allow_adjacent_fusion && !output.empty() && + output.back().relative_instance_location == + instruction.relative_instance_location) { + auto &previous{output.back()}; + if (instruction.type == InstructionIndex::AssertionTypeArrayBounded && + (previous.type == InstructionIndex::LoopItemsTypeStrictAny || + previous.type == InstructionIndex::LoopItemsTypeStrict)) { + ValueTypes types{}; + if (previous.type == InstructionIndex::LoopItemsTypeStrict) { + types.set(std::to_underlying(std::get(previous.value))); + } else { + types = std::get(previous.value); + } + + previous.type = InstructionIndex::LoopItemsTypeStrictAnySized; + previous.value = + ValueTypesWithSize{types, std::get(instruction.value)}; + return true; + } + + if ((instruction.type == InstructionIndex::LoopItemsTypeStrictAny || + instruction.type == InstructionIndex::LoopItemsTypeStrict) && + previous.type == InstructionIndex::AssertionTypeArrayBounded) { + ValueTypes types{}; + if (instruction.type == InstructionIndex::LoopItemsTypeStrict) { + types.set(std::to_underlying(std::get(instruction.value))); + } else { + types = std::get(instruction.value); + } + + previous.type = InstructionIndex::LoopItemsTypeStrictAnySized; + previous.value = + ValueTypesWithSize{types, std::get(previous.value)}; + return true; + } + } + if (is_parent_to_children_instruction(instruction.type)) { convert_to_property_type_assertions(instruction.children); } @@ -378,6 +422,7 @@ inline auto postprocess(std::vector &targets, auto ¤t_stats{statistics[current_target_index]}; std::vector worklist; + std::unordered_set position_indexed; std::vector> stack; stack.emplace_back(&target, 0); @@ -391,6 +436,10 @@ inline auto postprocess(std::vector &targets, } if (index < current->size()) { + if ((*current)[index].type == InstructionIndex::LogicalCondition) { + position_indexed.insert(&(*current)[index].children); + } + stack.emplace_back(current, index + 1); stack.emplace_back(&(*current)[index].children, 0); } else { @@ -457,7 +506,8 @@ inline auto postprocess(std::vector &targets, if (transform_instruction(instruction, result, extra, targets, statistics, current_stats, tweaks, - uses_dynamic_scopes)) + uses_dynamic_scopes, + !position_indexed.contains(current))) changed = true; } diff --git a/src/evaluator/evaluator_describe.cc b/src/evaluator/evaluator_describe.cc index e970478c3..9cb29105b 100644 --- a/src/evaluator/evaluator_describe.cc +++ b/src/evaluator/evaluator_describe.cc @@ -413,6 +413,21 @@ auto describe(const bool valid, const Instruction &step, return message.str(); } + if (step.type == + sourcemeta::blaze::InstructionIndex::LogicalSwitchPropertyString) { + assert(!step.children.empty()); + std::ostringstream message; + message << "The " << type_name(target.type()) + << " value was expected to validate against "; + if (step.children.size() > 1) { + message << "one of the " << step.children.size() << " given subschemas"; + } else { + message << "the given subschema"; + } + + return message.str(); + } + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalCondition) { std::ostringstream message; message << "The " << type_name(target.type()) @@ -1201,6 +1216,27 @@ auto describe(const bool valid, const Instruction &step, return message.str(); } + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopItemsTypeStrictAnySized) { + std::ostringstream message; + const auto &value{instruction_value(step)}; + const auto &[minimum, maximum, exhaustive] = value.second; + if (minimum == 0 && maximum.has_value()) { + message << "The value was expected to consist of an array of at most " + << maximum.value() << (maximum.value() == 1 ? " item" : " items"); + } else if (maximum.has_value()) { + message << "The value was expected to consist of an array of " << minimum + << " to " << maximum.value() + << (maximum.value() == 1 ? " item" : " items"); + } else { + message << "The value was expected to consist of an array of at least " + << minimum << (minimum == 1 ? " item" : " items"); + } + + message << " where every item is of the expected type"; + return message.str(); + } + if (step.type == sourcemeta::blaze::InstructionIndex::LoopContains) { assert(target.is_array()); std::ostringstream message; diff --git a/src/evaluator/evaluator_json.cc b/src/evaluator/evaluator_json.cc index d0b4d2ea6..28e014b50 100644 --- a/src/evaluator/evaluator_json.cc +++ b/src/evaluator/evaluator_json.cc @@ -43,6 +43,8 @@ auto value_from_json(const sourcemeta::core::JSON &wrapper) case 22: return sourcemeta::core::from_json(value); case 23: return sourcemeta::core::from_json(value); case 24: return sourcemeta::core::from_json(value); + case 25: return sourcemeta::core::from_json(value); + case 26: return sourcemeta::core::from_json(value); // clang-format on default: std::unreachable(); @@ -108,7 +110,9 @@ auto instructions_from_json( .schema_resource = std::move(schema_resource_result).value()}); // TODO: Maybe we should emplace here? - result.push_back({std::move(type_result).value(), + // Note that we conservatively consider every instruction as tracking, + // as the granular flags are not part of the serialised format + result.push_back({std::move(type_result).value(), true, std::move(relative_instance_location_result).value(), std::move(value_result).value(), std::move(children_result).value(), extra_index}); diff --git a/src/evaluator/include/sourcemeta/blaze/evaluator.h b/src/evaluator/include/sourcemeta/blaze/evaluator.h index 39f0b46ad..91ac8e065 100644 --- a/src/evaluator/include/sourcemeta/blaze/evaluator.h +++ b/src/evaluator/include/sourcemeta/blaze/evaluator.h @@ -44,7 +44,7 @@ struct Template { }; /// @ingroup evaluator -constexpr std::size_t JSON_VERSION{5}; +constexpr std::size_t JSON_VERSION{6}; /// @ingroup evaluator /// Parse a template from JSON diff --git a/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h b/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h index bf9fa62dc..c10447e18 100644 --- a/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h +++ b/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h @@ -12,6 +12,14 @@ #include #include +#if defined(SOURCEMETA_BLAZE_EVALUATOR_TRACE_OPCODES) +#include // std::ranges::sort +#include // std::array +#include // std::uint64_t +#include // std::atexit +#include // std::cerr +#endif + // TODO(C++23): Replace SOURCEMETA_ASSUME with [[assume]] when available // across all compilers (Clang 19, GCC 13) #if defined(__clang__) @@ -26,9 +34,13 @@ #define EVALUATE_PUSH() \ if constexpr (Track) { \ - context.evaluator->evaluate_path.push_back( \ - context.schema->extra[instruction.extra_index] \ - .relative_schema_location); \ + /* Without a callback, only instructions whose subtree can produce or */ \ + /* consume evaluation marks need to maintain the evaluate path */ \ + if (HasCallback || instruction.track) { \ + context.evaluator->evaluate_path.push_back( \ + context.schema->extra[instruction.extra_index] \ + .relative_schema_location); \ + } \ } \ if constexpr (HasCallback) { \ context.evaluator->instance_location.push_back( \ @@ -55,9 +67,11 @@ Evaluator::null); \ } \ if constexpr (Track) { \ - context.evaluator->evaluate_path.pop_back( \ - context.schema->extra[instruction.extra_index] \ - .relative_schema_location.size()); \ + if (HasCallback || instruction.track) { \ + context.evaluator->evaluate_path.pop_back( \ + context.schema->extra[instruction.extra_index] \ + .relative_schema_location.size()); \ + } \ } \ if constexpr (HasCallback) { \ context.evaluator->instance_location.pop_back( \ @@ -189,6 +203,45 @@ namespace sourcemeta::blaze::dispatch { using namespace sourcemeta::core; +#if defined(SOURCEMETA_BLAZE_EVALUATOR_TRACE_OPCODES) +// An opt-in debug measurement mode that aggregates per-opcode execution +// counts and prints a histogram to standard error on process exit +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +inline std::array opcode_counters{}; + +inline auto dump_opcode_counters() -> void { + std::array order{}; + for (std::size_t index = 0; index < order.size(); index++) { + order[index] = index; + } + std::ranges::sort(order, [](const auto left, const auto right) { + return opcode_counters[left] > opcode_counters[right]; + }); + + std::uint64_t total{0}; + for (const auto count : opcode_counters) { + total += count; + } + + std::cerr << "== Blaze evaluator opcode histogram (total " << total << ")\n"; + for (const auto index : order) { + if (opcode_counters[index] == 0) { + continue; + } + + std::cerr << opcode_counters[index] << "\t" + << sourcemeta::blaze::InstructionNames[index] << "\n"; + } +} + +inline auto register_opcode_counters() -> bool { + return std::atexit(dump_opcode_counters) == 0; +} + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +inline const bool opcode_counters_registered{register_opcode_counters()}; +#endif + inline auto resolve_target(const JSON::String *property_target, const JSON &instance) noexcept -> const JSON & { if (property_target) [[unlikely]] { @@ -1300,6 +1353,58 @@ INSTRUCTION_HANDLER(LogicalXor) { EVALUATE_END(LogicalXor); } +INSTRUCTION_HANDLER(LogicalSwitchPropertyString) { + EVALUATE_BEGIN_NO_PRECONDITION(LogicalSwitchPropertyString); + // Otherwise why are we emitting this instruction? + assert(!instruction.children.empty()); + const auto &target{ + resolve_instance(instance, instruction.relative_instance_location)}; + + // Every disjunct statically requires the discriminator property to be + // set to one of the strings in the map, on top of requiring the instance + // to be an object, so anything that doesn't match the map cannot + // possibly validate against any of the disjuncts. The only exception is + // the optional disjunct that can match non-object instances + const auto &value{assume_value(instruction.value)}; + const std::size_t selected{[&instruction, &target, &value]() { + if (target.is_object()) [[likely]] { + const auto &property{std::get<0>(value)}; + const auto *discriminator{target.try_at(property.first, property.second)}; + if (discriminator && discriminator->is_string()) [[likely]] { + const auto &indexes{std::get<1>(value)}; + const auto &content{discriminator->to_string()}; + const auto *index{indexes.try_at(content, indexes.hash(content))}; + if (index) [[likely]] { + return *index; + } + } + + return instruction.children.size(); + } else { + // Note that on no match, this expression results in the + // out-of-bounds sentinel we want anyway + return std::get<2>(value) - 1; + } + }()}; + + if (selected < instruction.children.size()) [[likely]] { + const auto &subinstruction{instruction.children[selected]}; + assert(subinstruction.type == + sourcemeta::blaze::InstructionIndex::ControlGroup); + SOURCEMETA_ASSUME(subinstruction.type == + sourcemeta::blaze::InstructionIndex::ControlGroup); + result = true; + for (const auto &child : subinstruction.children) { + if (!EVALUATE_RECURSE(child, target)) [[unlikely]] { + result = false; + break; + } + } + } + + EVALUATE_END(LogicalSwitchPropertyString); +} + INSTRUCTION_HANDLER(LogicalCondition) { EVALUATE_BEGIN_NO_PRECONDITION(LogicalCondition); result = true; @@ -1331,8 +1436,8 @@ INSTRUCTION_HANDLER(LogicalCondition) { : children_size}; result = true; if (consequence_start > 0) { - if constexpr (Track || HasCallback) { - if (track) { + if constexpr (Track) { + if (HasCallback || instruction.track) { context.evaluator->evaluate_path.pop_back( context.schema->extra[instruction.extra_index] .relative_schema_location.size()); @@ -1346,8 +1451,8 @@ INSTRUCTION_HANDLER(LogicalCondition) { } } - if constexpr (Track || HasCallback) { - if (track) { + if constexpr (Track) { + if (HasCallback || instruction.track) { context.evaluator->evaluate_path.push_back( context.schema->extra[instruction.extra_index] .relative_schema_location); @@ -1410,6 +1515,37 @@ INSTRUCTION_HANDLER(ControlGroupWhenDefinesDirect) { EVALUATE_END_PASS_THROUGH(ControlGroupWhenDefinesDirect); } +INSTRUCTION_HANDLER(ControlGroupWhenDefinesResolved) { + EVALUATE_BEGIN_PASS_THROUGH(ControlGroupWhenDefinesResolved); + assert(!instruction.children.empty()); + assert(instruction.relative_instance_location.empty()); + const auto &value{assume_value(instruction.value)}; + + if (instance.is_object()) [[likely]] { + // Note that in this control instruction, we resolve the property just + // once and let the children evaluate directly against its value + const auto *entry{instance.try_at(value.first, value.second)}; + if (entry) { + if constexpr (HasCallback) { + context.evaluator->instance_location.push_back(value.first); + } + + for (const auto &child : instruction.children) { + if (!EVALUATE_RECURSE(child, *entry)) [[unlikely]] { + result = false; + break; + } + } + + if constexpr (HasCallback) { + context.evaluator->instance_location.pop_back(); + } + } + } + + EVALUATE_END_PASS_THROUGH(ControlGroupWhenDefinesResolved); +} + INSTRUCTION_HANDLER(ControlGroupWhenType) { EVALUATE_BEGIN_PASS_THROUGH(ControlGroupWhenType); assert(!instruction.children.empty()); @@ -2340,6 +2476,37 @@ INSTRUCTION_HANDLER(LoopItemsTypeStrictAny) { EVALUATE_END(LoopItemsTypeStrictAny); } +INSTRUCTION_HANDLER(LoopItemsTypeStrictAnySized) { + EVALUATE_BEGIN_NO_PRECONDITION(LoopItemsTypeStrictAnySized); + const auto &target{ + resolve_instance(instance, instruction.relative_instance_location)}; + const auto &value{assume_value(instruction.value)}; + assert(value.first.any()); + const auto &[minimum, maximum, exhaustive] = value.second; + assert(!maximum.has_value() || maximum.value() >= minimum); + // Require early breaking + assert(!exhaustive); + SOURCEMETA_ASSUME(!exhaustive); + + if (target.type() == JSON::Type::Array) [[likely]] { + const auto size{target.array_size()}; + if (size >= minimum && (!maximum.has_value() || size <= maximum.value())) + [[likely]] { + result = true; + for (const auto &entry : target.as_array()) { + const auto type_index{ + std::to_underlying(effective_type_strict_real(entry))}; + if (!value.first.test(type_index)) [[unlikely]] { + result = false; + break; + } + } + } + } + + EVALUATE_END(LoopItemsTypeStrictAnySized); +} + INSTRUCTION_HANDLER(LoopItemsPropertiesExactlyTypeStrictHash) { EVALUATE_BEGIN_NO_PRECONDITION(LoopItemsPropertiesExactlyTypeStrictHash); const auto &target{ @@ -2647,7 +2814,7 @@ using DispatchHandler = bool (*)( template // Must have same order as InstructionIndex // NOLINTNEXTLINE(modernize-avoid-c-arrays) -static constexpr DispatchHandler handlers[100] = { +static constexpr DispatchHandler handlers[103] = { AssertionFail, AssertionDefines, AssertionDefinesStrict, @@ -2707,6 +2874,7 @@ static constexpr DispatchHandler handlers[100] = { LogicalOr, LogicalAnd, LogicalXor, + LogicalSwitchPropertyString, LogicalCondition, LogicalWhenType, LogicalWhenDefines, @@ -2736,6 +2904,7 @@ static constexpr DispatchHandler handlers[100] = { LoopItemsType, LoopItemsTypeStrict, LoopItemsTypeStrictAny, + LoopItemsTypeStrictAnySized, LoopItemsPropertiesExactlyTypeStrictHash, LoopItemsPropertiesExactlyTypeStrictHash3, LoopItemsIntegerBounded, @@ -2744,6 +2913,7 @@ static constexpr DispatchHandler handlers[100] = { ControlGroup, ControlGroupWhenDefines, ControlGroupWhenDefinesDirect, + ControlGroupWhenDefinesResolved, ControlGroupWhenType, ControlEvaluate, ControlDynamicAnchorJump, @@ -2762,6 +2932,10 @@ evaluate_instruction(const sourcemeta::blaze::Instruction &instruction, "likely due to infinite recursion"); } +#if defined(SOURCEMETA_BLAZE_EVALUATOR_TRACE_OPCODES) + opcode_counters[std::to_underlying(instruction.type)] += 1; +#endif + return handlers[std::to_underlying( instruction.type)](instruction, instance, depth, context); } @@ -2777,6 +2951,10 @@ inline auto evaluate_instruction_without_callback( "likely due to infinite recursion"); } +#if defined(SOURCEMETA_BLAZE_EVALUATOR_TRACE_OPCODES) + opcode_counters[std::to_underlying(instruction.type)] += 1; +#endif + DispatchContext plain_context{ context.schema, context.callback, context.evaluator, context.property_target}; diff --git a/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h b/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h index e3d39c345..d9d6ed5b7 100644 --- a/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h +++ b/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h @@ -79,6 +79,7 @@ enum class InstructionIndex : std::uint8_t { LogicalOr, LogicalAnd, LogicalXor, + LogicalSwitchPropertyString, LogicalCondition, LogicalWhenType, LogicalWhenDefines, @@ -108,6 +109,7 @@ enum class InstructionIndex : std::uint8_t { LoopItemsType, LoopItemsTypeStrict, LoopItemsTypeStrictAny, + LoopItemsTypeStrictAnySized, LoopItemsPropertiesExactlyTypeStrictHash, LoopItemsPropertiesExactlyTypeStrictHash3, LoopItemsIntegerBounded, @@ -116,6 +118,7 @@ enum class InstructionIndex : std::uint8_t { ControlGroup, ControlGroupWhenDefines, ControlGroupWhenDefinesDirect, + ControlGroupWhenDefinesResolved, ControlGroupWhenType, ControlEvaluate, ControlDynamicAnchorJump, @@ -184,6 +187,7 @@ constexpr std::string_view InstructionNames[] = { "LogicalOr", "LogicalAnd", "LogicalXor", + "LogicalSwitchPropertyString", "LogicalCondition", "LogicalWhenType", "LogicalWhenDefines", @@ -213,6 +217,7 @@ constexpr std::string_view InstructionNames[] = { "LoopItemsType", "LoopItemsTypeStrict", "LoopItemsTypeStrictAny", + "LoopItemsTypeStrictAnySized", "LoopItemsPropertiesExactlyTypeStrictHash", "LoopItemsPropertiesExactlyTypeStrictHash3", "LoopItemsIntegerBounded", @@ -221,6 +226,7 @@ constexpr std::string_view InstructionNames[] = { "ControlGroup", "ControlGroupWhenDefines", "ControlGroupWhenDefinesDirect", + "ControlGroupWhenDefinesResolved", "ControlGroupWhenType", "ControlEvaluate", "ControlDynamicAnchorJump", @@ -260,10 +266,14 @@ struct InstructionExtra { }; /// @ingroup evaluator -/// Represents a single instruction to be evaluated +/// Represents a single instruction to be evaluated. The `track` member +/// notes whether the instruction must maintain the evaluate path when +/// running a tracking template without a callback: only instructions +/// whose subtree can produce or consume evaluation marks need to // NOLINTNEXTLINE(bugprone-exception-escape) struct Instruction { InstructionIndex type; + bool track{true}; sourcemeta::core::Pointer relative_instance_location; Value value; Instructions children; diff --git a/src/evaluator/include/sourcemeta/blaze/evaluator_string_set.h b/src/evaluator/include/sourcemeta/blaze/evaluator_string_set.h index 9386a2e32..1da962269 100644 --- a/src/evaluator/include/sourcemeta/blaze/evaluator_string_set.h +++ b/src/evaluator/include/sourcemeta/blaze/evaluator_string_set.h @@ -7,10 +7,11 @@ #include -#include // std::ranges::sort -#include // std::optional -#include // std::pair, std::move -#include // std::vector +#include // std::ranges::lower_bound +#include // std::ranges::less +#include // std::optional +#include // std::pair, std::move +#include // std::vector namespace sourcemeta::blaze { @@ -57,19 +58,21 @@ class SOURCEMETA_BLAZE_EVALUATOR_EXPORT StringSet { inline auto insert(const string_type &value) -> void { const auto hash{this->hasher(value)}; if (!this->contains(value, hash)) { - this->data.emplace_back(value, hash); - std::ranges::sort(this->data, [](const auto &left, const auto &right) { - return left.first < right.first; - }); + this->data.emplace( + std::ranges::lower_bound( + this->data, value, std::ranges::less{}, + [](const auto &entry) -> const auto & { return entry.first; }), + value, hash); } } inline auto insert(string_type &&value) -> void { const auto hash{this->hasher(value)}; if (!this->contains(value, hash)) { - this->data.emplace_back(std::move(value), hash); - std::ranges::sort(this->data, [](const auto &left, const auto &right) { - return left.first < right.first; - }); + this->data.emplace( + std::ranges::lower_bound( + this->data, value, std::ranges::less{}, + [](const auto &entry) -> const auto & { return entry.first; }), + std::move(value), hash); } } diff --git a/src/evaluator/include/sourcemeta/blaze/evaluator_value.h b/src/evaluator/include/sourcemeta/blaze/evaluator_value.h index 38e59f7a5..e8312236d 100644 --- a/src/evaluator/include/sourcemeta/blaze/evaluator_value.h +++ b/src/evaluator/include/sourcemeta/blaze/evaluator_value.h @@ -190,6 +190,18 @@ using ValueIntegerBoundsWithSize = std::pair; using ValueObjectProperties = std::vector< std::tuple>; +/// @ingroup evaluator +/// Represents a discriminator property along with a map from its possible +/// string values to the index of the disjunct that pins each of those values. +/// The last component is the index plus one of the only disjunct that can +/// match non-object instances, where zero means no such disjunct exists +using ValuePropertySwitch = + std::tuple; + +/// @ingroup evaluator +/// Represents a JSON types bitmask combined with an array size range +using ValueTypesWithSize = std::pair; + /// @ingroup evaluator using Value = std::variant< ValueNone, ValueJSON, ValueSet, ValueString, ValueProperty, ValueStrings, @@ -197,7 +209,8 @@ using Value = std::variant< ValueRange, ValueBoolean, ValueNamedIndexes, ValueStringType, ValueStringMap, ValuePropertyFilter, ValueIndexPair, ValuePointer, ValueTypedProperties, ValueStringHashes, ValueTypedHashes, - ValueIntegerBounds, ValueIntegerBoundsWithSize, ValueObjectProperties>; + ValueIntegerBounds, ValueIntegerBoundsWithSize, ValueObjectProperties, + ValuePropertySwitch, ValueTypesWithSize>; } // namespace sourcemeta::blaze diff --git a/test/compiler/compiler_json_test.cc b/test/compiler/compiler_json_test.cc index 3d1870ae0..8093d6410 100644 --- a/test/compiler/compiler_json_test.cc +++ b/test/compiler/compiler_json_test.cc @@ -31,7 +31,7 @@ TEST(Compiler_JSON, example_1) { sourcemeta::blaze::default_schema_compiler)}; const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([ - 5, + 6, false, false, [ @@ -67,13 +67,13 @@ TEST(Compiler_JSON, example_2) { sourcemeta::blaze::default_schema_compiler)}; const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([ - 5, + 6, false, false, [ [ [ - 67, + 68, [ "additionalProperties" ], [], "#/additionalProperties", @@ -118,7 +118,7 @@ TEST(Compiler_JSON, example_3) { sourcemeta::blaze::default_schema_compiler)}; const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([ - 5, + 6, false, false, [ @@ -156,13 +156,13 @@ TEST(Compiler_JSON, example_4) { sourcemeta::blaze::default_schema_compiler)}; const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([ - 5, + 6, false, false, [ [ [ - 67, + 68, [ "additionalProperties" ], [], "https://example.com/top#/additionalProperties", @@ -233,7 +233,7 @@ TEST(Compiler_JSON, example_5) { sourcemeta::blaze::Mode::Exhaustive)}; const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([ - 5, + 6, false, true, [ @@ -278,13 +278,13 @@ TEST(Compiler_JSON, example_6) { sourcemeta::blaze::Mode::Exhaustive)}; const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([ - 5, + 6, false, true, [ [ [ - 67, + 68, [ "additionalProperties" ], [], "https://example.com/top#/additionalProperties", @@ -408,7 +408,7 @@ TEST(Compiler_JSON, invalid_1) { TEST(Compiler_JSON, invalid_version) { const auto input{sourcemeta::core::parse_json(R"JSON([ - 6, + 7, false, false, [ diff --git a/test/evaluator/evaluator_2019_09.json b/test/evaluator/evaluator_2019_09.json index 1c0664a57..bdbcc5b13 100644 --- a/test/evaluator/evaluator_2019_09.json +++ b/test/evaluator/evaluator_2019_09.json @@ -9,12 +9,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyType", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionType", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyType", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionType", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -55,12 +55,12 @@ "valid": false, "fast": { "pre": [ - [ "AssertionPropertyType", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionType", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyType", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ false, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionType", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ false, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -437,12 +437,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ], [ "AssertionType", "/additionalProperties/type", "#/additionalProperties/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionType", "/additionalProperties/type", "#/additionalProperties/type", "/bar" ], [ true, "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], @@ -527,14 +527,14 @@ "pre": [ [ "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], [ "AssertionType", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ], [ "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/baz" ] ], "post": [ [ true, "AssertionType", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], [ true, "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/baz" ], [ true, "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], @@ -2188,12 +2188,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ], [ "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/bar" ], [ true, "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ] ], @@ -2241,12 +2241,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ], [ "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], [ true, "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/bar" ], [ true, "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ] ], @@ -2297,12 +2297,12 @@ "valid": false, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ], [ "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], [ false, "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/bar" ], [ false, "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ] ], @@ -2350,12 +2350,12 @@ "valid": false, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ], [ "AssertionFail", "/unevaluatedProperties", "#/unevaluatedProperties", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ false, "AssertionFail", "/unevaluatedProperties", "#/unevaluatedProperties", "/bar" ], [ false, "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ] ], @@ -2718,12 +2718,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrictAny", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], + [ "AssertionTypeStrictAny", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ], [ "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrictAny", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrictAny", "/allOf/0/properties/foo/type", "#/allOf/0/properties/foo/type", "/foo" ], [ true, "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/bar" ], [ true, "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ] ], @@ -2775,11 +2775,11 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "LoopPropertiesExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ] ], "descriptions": [ @@ -4356,7 +4356,7 @@ [ "AssertionEqual", "/if/properties/known/const", "#/if/properties/known/const", "/known" ], [ "Evaluate", "/if/properties", "#/if/properties", "/known" ], [ "AssertionPropertyTypeEvaluate", "/then/properties/extra/type", "#/then/properties/extra/type", "/extra" ], - [ "AssertionPropertyTypeStrict", "/properties/known/type", "#/properties/known/type", "/known" ], + [ "AssertionTypeStrict", "/properties/known/type", "#/properties/known/type", "/known" ], [ "LoopPropertiesUnevaluatedExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ], [ "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/other" ] ], @@ -4365,7 +4365,7 @@ [ true, "Evaluate", "/if/properties", "#/if/properties", "/known" ], [ true, "AssertionPropertyTypeEvaluate", "/then/properties/extra/type", "#/then/properties/extra/type", "/extra" ], [ true, "LogicalCondition", "/if", "#/if", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/known/type", "#/properties/known/type", "/known" ], + [ true, "AssertionTypeStrict", "/properties/known/type", "#/properties/known/type", "/known" ], [ true, "AssertionTypeStrict", "/unevaluatedProperties/type", "#/unevaluatedProperties/type", "/other" ], [ true, "LoopPropertiesUnevaluatedExcept", "/unevaluatedProperties", "#/unevaluatedProperties", "" ] ], diff --git a/test/evaluator/evaluator_2020_12.json b/test/evaluator/evaluator_2020_12.json index 9d2082adb..be309a76a 100644 --- a/test/evaluator/evaluator_2020_12.json +++ b/test/evaluator/evaluator_2020_12.json @@ -3373,12 +3373,12 @@ "fast": { "pre": [ [ "LoopPropertiesStartsWith", "/patternProperties", "#/patternProperties", "" ], - [ "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ "AssertionTypeStrict", "/type", "#/type", "" ] ], "post": [ [ true, "LoopPropertiesStartsWith", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ true, "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ true, "AssertionTypeStrict", "/type", "#/type", "" ] ], "descriptions": [ @@ -3476,13 +3476,13 @@ "pre": [ [ "LogicalOr", "/anyOf", "#/anyOf", "" ], [ "AssertionDefines", "/anyOf/0/required", "#/anyOf/0/required", "" ], - [ "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ "AssertionTypeStrict", "/type", "#/type", "" ] ], "post": [ [ true, "AssertionDefines", "/anyOf/0/required", "#/anyOf/0/required", "" ], [ true, "LogicalOr", "/anyOf", "#/anyOf", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ true, "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ true, "AssertionTypeStrict", "/type", "#/type", "" ] ], "descriptions": [ @@ -3580,11 +3580,11 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ "AssertionTypeStrict", "/type", "#/type", "" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ true, "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ true, "AssertionTypeStrict", "/type", "#/type", "" ] ], "descriptions": [ @@ -3751,13 +3751,13 @@ "pre": [ [ "AssertionDefinesStrict", "/required", "#/required", "" ], [ "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], - [ "AssertionPropertyTypeStrictAny", "/properties/b/$ref/type", "#/$defs/num/type", "/b" ], + [ "AssertionTypeStrictAny", "/properties/b/$ref/type", "#/$defs/num/type", "/b" ], [ "AssertionGreaterEqual", "/properties/b/$ref/minimum", "#/$defs/num/minimum", "/b" ] ], "post": [ [ true, "AssertionDefinesStrict", "/required", "#/required", "" ], [ true, "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], - [ true, "AssertionPropertyTypeStrictAny", "/properties/b/$ref/type", "#/$defs/num/type", "/b" ], + [ true, "AssertionTypeStrictAny", "/properties/b/$ref/type", "#/$defs/num/type", "/b" ], [ true, "AssertionGreaterEqual", "/properties/b/$ref/minimum", "#/$defs/num/minimum", "/b" ] ], "descriptions": [ diff --git a/test/evaluator/evaluator_2020_12_test.cc b/test/evaluator/evaluator_2020_12_test.cc index f9ee9ce27..b6bc2dfdb 100644 --- a/test/evaluator/evaluator_2020_12_test.cc +++ b/test/evaluator/evaluator_2020_12_test.cc @@ -3920,3 +3920,47 @@ TEST(Evaluator_2020_12, x_assertion_nested_selective_valid_no_tweak_fast) { "The string value \"https://example.com\" was expected to represent a " "valid URI"); } + +TEST(Evaluator_2020_12, switch_property_string_const_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "const": "first" } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "const": "second" } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": "first" + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectPropertiesSimple, "/oneOf/0/properties", + "#/oneOf/0/properties", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionObjectPropertiesSimple, + "/oneOf/0/properties", "#/oneOf/0/properties", + ""); + EVALUATE_TRACE_POST_SUCCESS(1, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against the defined property subschemas"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} diff --git a/test/evaluator/evaluator_draft3.json b/test/evaluator/evaluator_draft3.json index d4834b714..11befcae6 100644 --- a/test/evaluator/evaluator_draft3.json +++ b/test/evaluator/evaluator_draft3.json @@ -1132,10 +1132,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type string" @@ -1169,10 +1169,10 @@ "valid": false, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] ], "post": [ - [ false, "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] + [ false, "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type string but it was of type integer" @@ -1331,10 +1331,10 @@ "fast": { "pre": [ [ "LogicalOr", "/type", "#/type", "" ], - [ "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], [ true, "LogicalOr", "/type", "#/type", "" ] ], "descriptions": [ @@ -1374,12 +1374,12 @@ "fast": { "pre": [ [ "LogicalOr", "/type", "#/type", "" ], - [ "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], - [ "AssertionPropertyTypeStrict", "/type/1/properties/bar/type", "#/type/1/properties/bar/type", "/bar" ] + [ "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/type/1/properties/bar/type", "#/type/1/properties/bar/type", "/bar" ] ], "post": [ - [ false, "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], - [ true, "AssertionPropertyTypeStrict", "/type/1/properties/bar/type", "#/type/1/properties/bar/type", "/bar" ], + [ false, "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/type/1/properties/bar/type", "#/type/1/properties/bar/type", "/bar" ], [ true, "LogicalOr", "/type", "#/type", "" ] ], "descriptions": [ @@ -1426,12 +1426,12 @@ "fast": { "pre": [ [ "LogicalOr", "/type", "#/type", "" ], - [ "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], - [ "AssertionPropertyTypeStrict", "/type/1/properties/bar/type", "#/type/1/properties/bar/type", "/bar" ] + [ "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/type/1/properties/bar/type", "#/type/1/properties/bar/type", "/bar" ] ], "post": [ - [ false, "AssertionPropertyTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], - [ false, "AssertionPropertyTypeStrict", "/type/1/properties/bar/type", "#/type/1/properties/bar/type", "/bar" ], + [ false, "AssertionTypeStrict", "/type/0/properties/foo/type", "#/type/0/properties/foo/type", "/foo" ], + [ false, "AssertionTypeStrict", "/type/1/properties/bar/type", "#/type/1/properties/bar/type", "/bar" ], [ false, "LogicalOr", "/type", "#/type", "" ] ], "descriptions": [ @@ -1547,11 +1547,11 @@ "pre": [ [ "LogicalOr", "/type", "#/type", "" ], [ "AssertionTypeStrict", "/type", "#/type", "" ], - [ "AssertionPropertyTypeStrict", "/type/1/properties/foo/type", "#/type/1/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/type/1/properties/foo/type", "#/type/1/properties/foo/type", "/foo" ] ], "post": [ [ false, "AssertionTypeStrict", "/type", "#/type", "" ], - [ true, "AssertionPropertyTypeStrict", "/type/1/properties/foo/type", "#/type/1/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/type/1/properties/foo/type", "#/type/1/properties/foo/type", "/foo" ], [ true, "LogicalOr", "/type", "#/type", "" ] ], "descriptions": [ @@ -1593,11 +1593,11 @@ "pre": [ [ "LogicalOr", "/type", "#/type", "" ], [ "AssertionTypeStrict", "/type", "#/type", "" ], - [ "AssertionPropertyTypeStrict", "/type/1/properties/foo/type", "#/type/1/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/type/1/properties/foo/type", "#/type/1/properties/foo/type", "/foo" ] ], "post": [ [ false, "AssertionTypeStrict", "/type", "#/type", "" ], - [ false, "AssertionPropertyTypeStrict", "/type/1/properties/foo/type", "#/type/1/properties/foo/type", "/foo" ], + [ false, "AssertionTypeStrict", "/type/1/properties/foo/type", "#/type/1/properties/foo/type", "/foo" ], [ false, "LogicalOr", "/type", "#/type", "" ] ], "descriptions": [ @@ -7297,15 +7297,9 @@ "instance": { "foo": { "bar": {} } }, "valid": true, "fast": { - "pre": [ - [ "ControlJump", "/properties/foo/$ref/$ref", "#/definitions/three/$ref", "/foo" ] - ], - "post": [ - [ true, "ControlJump", "/properties/foo/$ref/$ref", "#/definitions/three/$ref", "/foo" ] - ], - "descriptions": [ - "The object value was expected to validate against the referenced schema" - ] + "pre": [], + "post": [], + "descriptions": [] }, "exhaustive": { "pre": [ @@ -8200,12 +8194,12 @@ "pre": [ [ "AssertionTypeStrict", "/extends/0/type", "#/extends/0/type", "" ], [ "AssertionDefines", "/extends/1/properties", "#/extends/1/properties", "" ], - [ "AssertionPropertyTypeStrict", "/extends/1/properties/foo/type", "#/extends/1/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/extends/1/properties/foo/type", "#/extends/1/properties/foo/type", "/foo" ] ], "post": [ [ true, "AssertionTypeStrict", "/extends/0/type", "#/extends/0/type", "" ], [ true, "AssertionDefines", "/extends/1/properties", "#/extends/1/properties", "" ], - [ true, "AssertionPropertyTypeStrict", "/extends/1/properties/foo/type", "#/extends/1/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/extends/1/properties/foo/type", "#/extends/1/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type object", @@ -9791,12 +9785,12 @@ "valid": false, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ false, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ false, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -9831,12 +9825,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -9932,14 +9926,14 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], [ "AssertionRegex", "/properties/foo/pattern", "#/properties/foo/pattern", "/foo" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], [ true, "AssertionRegex", "/properties/foo/pattern", "#/properties/foo/pattern", "/foo" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -9981,14 +9975,14 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "AssertionRegex", "/properties/bar/pattern", "#/properties/bar/pattern", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionRegex", "/properties/bar/pattern", "#/properties/bar/pattern", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -10064,10 +10058,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type number" @@ -10102,7 +10096,7 @@ "fast": { "pre": [ [ "AssertionRegex", "/properties/bar/pattern", "#/properties/bar/pattern", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], [ "LoopItems", "/properties/foo/items", "#/properties/foo/items", "/foo" ], [ "AssertionDivisible", "/properties/foo/items/divisibleBy", "#/properties/foo/items/divisibleBy", "/foo/0" ], [ "AssertionLessEqual", "/properties/foo/items/maximum", "#/properties/foo/items/maximum", "/foo/0" ], @@ -10110,7 +10104,7 @@ ], "post": [ [ true, "AssertionRegex", "/properties/bar/pattern", "#/properties/bar/pattern", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], [ true, "AssertionDivisible", "/properties/foo/items/divisibleBy", "#/properties/foo/items/divisibleBy", "/foo/0" ], [ true, "AssertionLessEqual", "/properties/foo/items/maximum", "#/properties/foo/items/maximum", "/foo/0" ], [ true, "AssertionTypeStrictAny", "/properties/foo/items/type", "#/properties/foo/items/type", "/foo/0" ], @@ -10169,11 +10163,11 @@ "fast": { "pre": [ [ "AssertionEqual", "/properties/foo/enum", "#/properties/foo/enum", "/foo" ], - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] ], "post": [ [ true, "AssertionEqual", "/properties/foo/enum", "#/properties/foo/enum", "/foo" ], - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] ], "descriptions": [ "The string value \"baz\" was expected to equal the string constant \"baz\"", @@ -10337,10 +10331,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer" @@ -10371,10 +10365,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer, or string and it was of type integer" @@ -10443,12 +10437,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ], [ "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/bar" ], [ true, "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], @@ -10493,14 +10487,14 @@ "pre": [ [ "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], [ "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ], [ "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/baz" ] ], "post": [ [ true, "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], [ true, "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/baz" ], [ true, "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], @@ -10553,12 +10547,12 @@ "pre": [ [ "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], [ "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ [ true, "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], [ true, "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -10601,12 +10595,12 @@ "pre": [ [ "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], [ "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ [ true, "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], [ true, "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -11141,10 +11135,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type string" @@ -11530,14 +11524,14 @@ "fast": { "pre": [ [ "AssertionDefinesAll", "/properties", "#/properties", "" ], - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], "post": [ [ true, "AssertionDefinesAll", "/properties", "#/properties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], "descriptions": [ diff --git a/test/evaluator/evaluator_draft4.json b/test/evaluator/evaluator_draft4.json index 8451fdbcf..7b1d36942 100644 --- a/test/evaluator/evaluator_draft4.json +++ b/test/evaluator/evaluator_draft4.json @@ -1092,15 +1092,9 @@ "instance": { "foo": { "bar": {} } }, "valid": true, "fast": { - "pre": [ - [ "ControlJump", "/properties/foo/$ref/$ref", "#/definitions/three/$ref", "/foo" ] - ], - "post": [ - [ true, "ControlJump", "/properties/foo/$ref/$ref", "#/definitions/three/$ref", "/foo" ] - ], - "descriptions": [ - "The object value was expected to validate against the referenced schema" - ] + "pre": [], + "post": [], + "descriptions": [] }, "exhaustive": { "pre": [ @@ -1136,12 +1130,12 @@ "valid": false, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ false, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ false, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -1176,12 +1170,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -1348,14 +1342,14 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], [ "AssertionRegex", "/properties/foo/pattern", "#/properties/foo/pattern", "/foo" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], [ true, "AssertionRegex", "/properties/foo/pattern", "#/properties/foo/pattern", "/foo" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -1397,14 +1391,14 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "AssertionRegex", "/properties/bar/pattern", "#/properties/bar/pattern", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionRegex", "/properties/bar/pattern", "#/properties/bar/pattern", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -1480,10 +1474,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type number" @@ -1518,7 +1512,7 @@ "fast": { "pre": [ [ "AssertionRegex", "/properties/bar/pattern", "#/properties/bar/pattern", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], [ "LoopItems", "/properties/foo/items", "#/properties/foo/items", "/foo" ], [ "AssertionLessEqual", "/properties/foo/items/maximum", "#/properties/foo/items/maximum", "/foo/0" ], [ "AssertionDivisible", "/properties/foo/items/multipleOf", "#/properties/foo/items/multipleOf", "/foo/0" ], @@ -1526,7 +1520,7 @@ ], "post": [ [ true, "AssertionRegex", "/properties/bar/pattern", "#/properties/bar/pattern", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], [ true, "AssertionLessEqual", "/properties/foo/items/maximum", "#/properties/foo/items/maximum", "/foo/0" ], [ true, "AssertionDivisible", "/properties/foo/items/multipleOf", "#/properties/foo/items/multipleOf", "/foo/0" ], [ true, "AssertionTypeStrictAny", "/properties/foo/items/type", "#/properties/foo/items/type", "/foo/0" ], @@ -1585,11 +1579,11 @@ "fast": { "pre": [ [ "AssertionEqual", "/properties/foo/enum", "#/properties/foo/enum", "/foo" ], - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] ], "post": [ [ true, "AssertionEqual", "/properties/foo/enum", "#/properties/foo/enum", "/foo" ], - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ] ], "descriptions": [ "The string value \"baz\" was expected to equal the string constant \"baz\"", @@ -1825,10 +1819,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer" @@ -1859,10 +1853,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrictAny", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer, or string and it was of type integer" @@ -2433,12 +2427,12 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ], [ "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/bar" ], [ true, "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], @@ -2483,14 +2477,14 @@ "pre": [ [ "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], [ "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ], [ "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/baz" ] ], "post": [ [ true, "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], [ true, "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "AssertionTypeStrict", "/additionalProperties/type", "#/additionalProperties/type", "/baz" ], [ true, "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], @@ -2543,12 +2537,12 @@ "pre": [ [ "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], [ "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ [ true, "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], [ true, "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -2591,12 +2585,12 @@ "pre": [ [ "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], [ "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ [ true, "AssertionTypeStrict", "/patternProperties/^bar$/type", "#/patternProperties/%5Ebar$/type", "/bar" ], [ true, "LoopPropertiesRegex", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer", @@ -2860,14 +2854,14 @@ "fast": { "pre": [ [ "AssertionDefinesAll", "/required", "#/required", "" ], - [ "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], "post": [ [ true, "AssertionDefinesAll", "/required", "#/required", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], - [ true, "AssertionPropertyTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/properties/bar/type", "#/properties/bar/type", "/bar" ], + [ true, "AssertionTypeStrict", "/properties/foo/type", "#/properties/foo/type", "/foo" ], [ true, "LoopPropertiesExcept", "/additionalProperties", "#/additionalProperties", "" ] ], "descriptions": [ @@ -3450,12 +3444,12 @@ "fast": { "pre": [ [ "LogicalNot", "/not", "#/not", "" ], - [ "AssertionPropertyTypeStrict", "/not/properties/foo/type", "#/not/properties/foo/type", "/foo" ], + [ "AssertionTypeStrict", "/not/properties/foo/type", "#/not/properties/foo/type", "/foo" ], [ "LoopPropertiesExcept", "/not/additionalProperties", "#/not/additionalProperties", "" ], [ "AssertionTypeStrict", "/not/additionalProperties/type", "#/not/additionalProperties/type", "/bar" ] ], "post": [ - [ true, "AssertionPropertyTypeStrict", "/not/properties/foo/type", "#/not/properties/foo/type", "/foo" ], + [ true, "AssertionTypeStrict", "/not/properties/foo/type", "#/not/properties/foo/type", "/foo" ], [ false, "AssertionTypeStrict", "/not/additionalProperties/type", "#/not/additionalProperties/type", "/bar" ], [ false, "LoopPropertiesExcept", "/not/additionalProperties", "#/not/additionalProperties", "" ], [ true, "LogicalNot", "/not", "#/not", "" ] @@ -4236,12 +4230,12 @@ [ "LogicalOr", "/anyOf", "#/anyOf", "" ], [ "AssertionEqual", "/anyOf/0/properties/version/enum", "#/anyOf/0/properties/version/enum", "/version" ], [ "AssertionEqual", "/anyOf/1/properties/version/enum", "#/anyOf/1/properties/version/enum", "/version" ], - [ "AssertionPropertyTypeStrict", "/anyOf/1/properties/one/type", "#/anyOf/1/properties/one/type", "/one" ] + [ "AssertionTypeStrict", "/anyOf/1/properties/one/type", "#/anyOf/1/properties/one/type", "/one" ] ], "post": [ [ false, "AssertionEqual", "/anyOf/0/properties/version/enum", "#/anyOf/0/properties/version/enum", "/version" ], [ true, "AssertionEqual", "/anyOf/1/properties/version/enum", "#/anyOf/1/properties/version/enum", "/version" ], - [ true, "AssertionPropertyTypeStrict", "/anyOf/1/properties/one/type", "#/anyOf/1/properties/one/type", "/one" ], + [ true, "AssertionTypeStrict", "/anyOf/1/properties/one/type", "#/anyOf/1/properties/one/type", "/one" ], [ true, "LogicalOr", "/anyOf", "#/anyOf", "" ] ], "descriptions": [ @@ -4601,13 +4595,13 @@ [ "LogicalXor", "/oneOf", "#/oneOf", "" ], [ "AssertionEqual", "/oneOf/0/properties/version/enum", "#/oneOf/0/properties/version/enum", "/version" ], [ "AssertionEqual", "/oneOf/1/properties/version/enum", "#/oneOf/1/properties/version/enum", "/version" ], - [ "AssertionPropertyTypeStrict", "/oneOf/1/properties/one/type", "#/oneOf/1/properties/one/type", "/one" ], + [ "AssertionTypeStrict", "/oneOf/1/properties/one/type", "#/oneOf/1/properties/one/type", "/one" ], [ "AssertionEqual", "/oneOf/2/properties/version/enum", "#/oneOf/2/properties/version/enum", "/version" ] ], "post": [ [ false, "AssertionEqual", "/oneOf/0/properties/version/enum", "#/oneOf/0/properties/version/enum", "/version" ], [ true, "AssertionEqual", "/oneOf/1/properties/version/enum", "#/oneOf/1/properties/version/enum", "/version" ], - [ true, "AssertionPropertyTypeStrict", "/oneOf/1/properties/one/type", "#/oneOf/1/properties/one/type", "/one" ], + [ true, "AssertionTypeStrict", "/oneOf/1/properties/one/type", "#/oneOf/1/properties/one/type", "/one" ], [ false, "AssertionEqual", "/oneOf/2/properties/version/enum", "#/oneOf/2/properties/version/enum", "/version" ], [ true, "LogicalXor", "/oneOf", "#/oneOf", "" ] ], @@ -11479,12 +11473,12 @@ "fast": { "pre": [ [ "LoopPropertiesStartsWith", "/patternProperties", "#/patternProperties", "" ], - [ "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ "AssertionTypeStrict", "/type", "#/type", "" ] ], "post": [ [ true, "LoopPropertiesStartsWith", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ true, "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ true, "AssertionTypeStrict", "/type", "#/type", "" ] ], "descriptions": [ @@ -11576,13 +11570,13 @@ "pre": [ [ "LogicalOr", "/anyOf", "#/anyOf", "" ], [ "AssertionDefines", "/anyOf/0/required", "#/anyOf/0/required", "" ], - [ "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ "AssertionTypeStrict", "/type", "#/type", "" ] ], "post": [ [ true, "AssertionDefines", "/anyOf/0/required", "#/anyOf/0/required", "" ], [ true, "LogicalOr", "/anyOf", "#/anyOf", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ true, "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ true, "AssertionTypeStrict", "/type", "#/type", "" ] ], "descriptions": [ diff --git a/test/evaluator/evaluator_draft4_test.cc b/test/evaluator/evaluator_draft4_test.cc index 7af6b09ab..6f9b8c190 100644 --- a/test/evaluator/evaluator_draft4_test.cc +++ b/test/evaluator/evaluator_draft4_test.cc @@ -790,11 +790,10 @@ TEST(Evaluator_draft4, properties_20) { EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 1, ""); - EVALUATE_TRACE_PRE(0, AssertionPropertyTypeStrict, "/properties/foo/type", + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/properties/foo/type", "#/properties/foo/type", "/foo"); - EVALUATE_TRACE_POST_FAILURE(0, AssertionPropertyTypeStrict, - "/properties/foo/type", "#/properties/foo/type", - "/foo"); + EVALUATE_TRACE_POST_FAILURE(0, AssertionTypeStrict, "/properties/foo/type", + "#/properties/foo/type", "/foo"); EVALUATE_TRACE_POST_DESCRIBE( instance, 0, "The value was expected to be of type integer but it was of type number"); @@ -3335,3 +3334,84 @@ TEST(Evaluator_draft4, EVALUATE_TRACE_POST_DESCRIBE(instance, 0, "The value was expected to be of type integer"); } + +TEST(Evaluator_draft4, switch_property_string_enum_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "anyOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first", "alias" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": "alias" + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/anyOf", "#/anyOf", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectPropertiesSimple, "/anyOf/0/properties", + "#/anyOf/0/properties", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionObjectPropertiesSimple, + "/anyOf/0/properties", "#/anyOf/0/properties", + ""); + EVALUATE_TRACE_POST_SUCCESS(1, LogicalSwitchPropertyString, "/anyOf", + "#/anyOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against the defined property subschemas"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft4, switch_property_string_duplicate_values_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "anyOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] }, + "value": { "type": "integer" } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] }, + "value": { "type": "string" } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": "first", + "value": "foo" + })JSON")}; + + sourcemeta::blaze::Evaluator evaluator; + const auto compiled_schema{ + sourcemeta::blaze::compile(schema, sourcemeta::blaze::schema_walker, + sourcemeta::blaze::schema_resolver, + sourcemeta::blaze::default_schema_compiler, + sourcemeta::blaze::Mode::FastValidation)}; + EXPECT_TRUE(evaluator.validate(compiled_schema, instance)); +} diff --git a/test/evaluator/evaluator_draft6.json b/test/evaluator/evaluator_draft6.json index 60efd9224..4be764594 100644 --- a/test/evaluator/evaluator_draft6.json +++ b/test/evaluator/evaluator_draft6.json @@ -2742,10 +2742,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer" @@ -2776,10 +2776,10 @@ "valid": true, "fast": { "pre": [ - [ "AssertionPropertyType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ true, "AssertionPropertyType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ true, "AssertionType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer" @@ -2810,10 +2810,10 @@ "valid": false, "fast": { "pre": [ - [ "AssertionPropertyType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ "AssertionType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "post": [ - [ false, "AssertionPropertyType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] + [ false, "AssertionType", "/properties/foo/type", "#/properties/foo/type", "/foo" ] ], "descriptions": [ "The value was expected to be of type integer but it was of type number" @@ -4933,12 +4933,12 @@ "fast": { "pre": [ [ "LoopPropertiesStartsWith", "/patternProperties", "#/patternProperties", "" ], - [ "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ "AssertionTypeStrict", "/type", "#/type", "" ] ], "post": [ [ true, "LoopPropertiesStartsWith", "/patternProperties", "#/patternProperties", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ true, "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ true, "AssertionTypeStrict", "/type", "#/type", "" ] ], "descriptions": [ @@ -5030,13 +5030,13 @@ "pre": [ [ "LogicalOr", "/anyOf", "#/anyOf", "" ], [ "AssertionDefines", "/anyOf/0/required", "#/anyOf/0/required", "" ], - [ "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ "AssertionTypeStrict", "/type", "#/type", "" ] ], "post": [ [ true, "AssertionDefines", "/anyOf/0/required", "#/anyOf/0/required", "" ], [ true, "LogicalOr", "/anyOf", "#/anyOf", "" ], - [ true, "AssertionPropertyTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], + [ true, "AssertionTypeStrict", "/properties/a/type", "#/properties/a/type", "/a" ], [ true, "AssertionTypeStrict", "/type", "#/type", "" ] ], "descriptions": [ diff --git a/test/evaluator/evaluator_draft6_test.cc b/test/evaluator/evaluator_draft6_test.cc index b4e5e7628..7899b859a 100644 --- a/test/evaluator/evaluator_draft6_test.cc +++ b/test/evaluator/evaluator_draft6_test.cc @@ -990,9 +990,9 @@ TEST(Evaluator_draft6, properties_4) { EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 1, ""); - EVALUATE_TRACE_PRE(0, AssertionPropertyType, "/properties/foo/type", + EVALUATE_TRACE_PRE(0, AssertionType, "/properties/foo/type", "#/properties/foo/type", "/foo"); - EVALUATE_TRACE_POST_SUCCESS(0, AssertionPropertyType, "/properties/foo/type", + EVALUATE_TRACE_POST_SUCCESS(0, AssertionType, "/properties/foo/type", "#/properties/foo/type", "/foo"); EVALUATE_TRACE_POST_DESCRIBE(instance, 0, "The value was expected to be of type integer"); @@ -1013,9 +1013,9 @@ TEST(Evaluator_draft6, properties_5) { EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 1, ""); - EVALUATE_TRACE_PRE(0, AssertionPropertyType, "/properties/foo/type", + EVALUATE_TRACE_PRE(0, AssertionType, "/properties/foo/type", "#/properties/foo/type", "/foo"); - EVALUATE_TRACE_POST_SUCCESS(0, AssertionPropertyType, "/properties/foo/type", + EVALUATE_TRACE_POST_SUCCESS(0, AssertionType, "/properties/foo/type", "#/properties/foo/type", "/foo"); EVALUATE_TRACE_POST_DESCRIBE(instance, 0, "The value was expected to be of type integer"); @@ -1036,9 +1036,9 @@ TEST(Evaluator_draft6, properties_6) { EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 1, ""); - EVALUATE_TRACE_PRE(0, AssertionPropertyType, "/properties/foo/type", + EVALUATE_TRACE_PRE(0, AssertionType, "/properties/foo/type", "#/properties/foo/type", "/foo"); - EVALUATE_TRACE_POST_FAILURE(0, AssertionPropertyType, "/properties/foo/type", + EVALUATE_TRACE_POST_FAILURE(0, AssertionType, "/properties/foo/type", "#/properties/foo/type", "/foo"); EVALUATE_TRACE_POST_DESCRIBE( instance, 0, @@ -3072,3 +3072,144 @@ TEST(Evaluator_draft6, EVALUATE_TRACE_POST_DESCRIBE(instance, 0, "The value was expected to be of type integer"); } + +TEST(Evaluator_draft6, switch_property_string_const_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "anyOf": [ + { + "type": "object", + "required": [ "shape", "radius" ], + "properties": { + "shape": { "const": "circle" }, + "radius": { "type": "number" } + } + }, + { + "type": "object", + "required": [ "shape", "side" ], + "properties": { + "shape": { "const": "square" }, + "side": { "type": "number" } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "shape": "square", + "side": 3 + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/anyOf", "#/anyOf", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectPropertiesSimple, "/anyOf/1/properties", + "#/anyOf/1/properties", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionObjectPropertiesSimple, + "/anyOf/1/properties", "#/anyOf/1/properties", + ""); + EVALUATE_TRACE_POST_SUCCESS(1, LogicalSwitchPropertyString, "/anyOf", + "#/anyOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against the defined property subschemas"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft6, switch_property_string_through_references_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "definitions": { + "circle": { + "type": "object", + "required": [ "shape", "radius" ], + "properties": { + "shape": { "const": "circle" }, + "radius": { "type": "number" } + } + }, + "square": { + "type": "object", + "required": [ "shape", "side" ], + "properties": { + "shape": { "const": "square" }, + "side": { "type": "number" } + } + } + }, + "anyOf": [ + { "allOf": [ { "$ref": "#/definitions/circle" } ] }, + { "allOf": [ { "$ref": "#/definitions/square" } ] } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "shape": "square", + "side": 3 + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/anyOf", "#/anyOf", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectPropertiesSimple, + "/anyOf/1/allOf/0/$ref/properties", + "#/definitions/square/properties", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionObjectPropertiesSimple, + "/anyOf/1/allOf/0/$ref/properties", + "#/definitions/square/properties", ""); + EVALUATE_TRACE_POST_SUCCESS(1, LogicalSwitchPropertyString, "/anyOf", + "#/anyOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against the defined property subschemas"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft6, switch_property_string_through_references_failure_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "definitions": { + "circle": { + "type": "object", + "required": [ "shape", "radius" ], + "properties": { + "shape": { "const": "circle" }, + "radius": { "type": "number" } + } + }, + "square": { + "type": "object", + "required": [ "shape", "side" ], + "properties": { + "shape": { "const": "square" }, + "side": { "type": "number" } + } + } + }, + "anyOf": [ + { "allOf": [ { "$ref": "#/definitions/circle" } ] }, + { "allOf": [ { "$ref": "#/definitions/square" } ] } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "shape": "oval" + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 1, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/anyOf", "#/anyOf", ""); + EVALUATE_TRACE_POST_FAILURE(0, LogicalSwitchPropertyString, "/anyOf", + "#/anyOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} diff --git a/test/evaluator/evaluator_draft7_test.cc b/test/evaluator/evaluator_draft7_test.cc index 02cfa67b9..3d05054ee 100644 --- a/test/evaluator/evaluator_draft7_test.cc +++ b/test/evaluator/evaluator_draft7_test.cc @@ -2993,3 +2993,518 @@ TEST(Evaluator_draft7, x_assertion_true_without_format_no_tweak_fast) { EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 0, ""); } + +TEST(Evaluator_draft7, switch_property_string_first_disjunct_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] }, + "value": { "type": "integer" } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] }, + "name": { "type": "string" } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": "first", + "value": 1 + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectPropertiesSimple, "/oneOf/0/properties", + "#/oneOf/0/properties", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionObjectPropertiesSimple, + "/oneOf/0/properties", "#/oneOf/0/properties", + ""); + EVALUATE_TRACE_POST_SUCCESS(1, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against the defined property subschemas"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_second_disjunct_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] }, + "value": { "type": "integer" } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] }, + "name": { "type": "string" } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": "second", + "name": "foo" + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectPropertiesSimple, "/oneOf/1/properties", + "#/oneOf/1/properties", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionObjectPropertiesSimple, + "/oneOf/1/properties", "#/oneOf/1/properties", + ""); + EVALUATE_TRACE_POST_SUCCESS(1, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against the defined property subschemas"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_unknown_value_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": "third" + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 1, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_POST_FAILURE(0, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_missing_discriminator_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "other": 1 + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 1, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_POST_FAILURE(0, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_non_string_discriminator_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": 5 + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 1, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_POST_FAILURE(0, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_non_object_instance_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{"foo"}; + + EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 1, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_POST_FAILURE(0, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The string value was expected to validate " + "against one of the 2 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_nullable_null_instance_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { "type": "null" }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{nullptr}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/oneOf/0/type", "#/oneOf/0/type", + ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/oneOf/0/type", + "#/oneOf/0/type", ""); + EVALUATE_TRACE_POST_SUCCESS(1, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type null"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The null value was expected to validate " + "against one of the 3 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_nullable_object_instance_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { "type": "null" }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": "first" + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_PRE(1, AssertionObjectPropertiesSimple, "/oneOf/1/properties", + "#/oneOf/1/properties", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionObjectPropertiesSimple, + "/oneOf/1/properties", "#/oneOf/1/properties", + ""); + EVALUATE_TRACE_POST_SUCCESS(1, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The object value was expected to validate " + "against the defined property subschemas"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The object value was expected to validate " + "against one of the 3 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_nullable_string_instance_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { "type": "null" }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{"foo"}; + + EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, LogicalSwitchPropertyString, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/oneOf/0/type", "#/oneOf/0/type", + ""); + EVALUATE_TRACE_POST_FAILURE(0, AssertionTypeStrict, "/oneOf/0/type", + "#/oneOf/0/type", ""); + EVALUATE_TRACE_POST_FAILURE(1, LogicalSwitchPropertyString, "/oneOf", + "#/oneOf", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type null " + "but it was of type string"); + EVALUATE_TRACE_POST_DESCRIBE(instance, 1, + "The string value was expected to validate " + "against one of the 3 given subschemas"); +} + +TEST(Evaluator_draft7, switch_property_string_exhaustive_keeps_disjunction) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "first" ] } + } + }, + { + "type": "object", + "required": [ "kind" ], + "properties": { + "kind": { "enum": [ "second" ] } + } + } + ] + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "kind": "first" + })JSON")}; + + EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 8, ""); + + EVALUATE_TRACE_PRE(0, LogicalXor, "/oneOf", "#/oneOf", ""); + EVALUATE_TRACE_PRE(1, AssertionDefinesStrict, "/oneOf/0/required", + "#/oneOf/0/required", ""); + EVALUATE_TRACE_PRE(2, LogicalWhenType, "/oneOf/0/properties", + "#/oneOf/0/properties", ""); + EVALUATE_TRACE_PRE(3, AssertionEqual, "/oneOf/0/properties/kind/enum", + "#/oneOf/0/properties/kind/enum", "/kind"); + EVALUATE_TRACE_PRE(4, AssertionTypeStrict, "/oneOf/0/type", "#/oneOf/0/type", + ""); + EVALUATE_TRACE_PRE(5, AssertionDefinesStrict, "/oneOf/1/required", + "#/oneOf/1/required", ""); + EVALUATE_TRACE_PRE(6, LogicalWhenType, "/oneOf/1/properties", + "#/oneOf/1/properties", ""); + EVALUATE_TRACE_PRE(7, AssertionEqual, "/oneOf/1/properties/kind/enum", + "#/oneOf/1/properties/kind/enum", "/kind"); +} + +TEST(Evaluator_draft7, resolved_property_group_multiple_assertions_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "foo": { + "minimum": 0, + "multipleOf": 2 + } + } + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "foo": 4 + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, AssertionGreaterEqual, "/properties/foo/minimum", + "#/properties/foo/minimum", "/foo"); + EVALUATE_TRACE_PRE(1, AssertionDivisible, "/properties/foo/multipleOf", + "#/properties/foo/multipleOf", "/foo"); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionGreaterEqual, + "/properties/foo/minimum", + "#/properties/foo/minimum", "/foo"); + EVALUATE_TRACE_POST_SUCCESS(1, AssertionDivisible, + "/properties/foo/multipleOf", + "#/properties/foo/multipleOf", "/foo"); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The integer value 4 was expected to be " + "greater than or equal to the integer 0"); + EVALUATE_TRACE_POST_DESCRIBE( + instance, 1, + "The integer value 4 was expected to be divisible by the integer 2"); +} + +TEST(Evaluator_draft7, + resolved_property_group_multiple_assertions_failure_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "foo": { + "minimum": 0, + "multipleOf": 2 + } + } + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "foo": 3 + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 2, ""); + + EVALUATE_TRACE_PRE(0, AssertionGreaterEqual, "/properties/foo/minimum", + "#/properties/foo/minimum", "/foo"); + EVALUATE_TRACE_PRE(1, AssertionDivisible, "/properties/foo/multipleOf", + "#/properties/foo/multipleOf", "/foo"); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionGreaterEqual, + "/properties/foo/minimum", + "#/properties/foo/minimum", "/foo"); + EVALUATE_TRACE_POST_FAILURE(1, AssertionDivisible, + "/properties/foo/multipleOf", + "#/properties/foo/multipleOf", "/foo"); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The integer value 3 was expected to be " + "greater than or equal to the integer 0"); + EVALUATE_TRACE_POST_DESCRIBE( + instance, 1, + "The integer value 3 was expected to be divisible by the integer 2"); +} + +TEST(Evaluator_draft7, resolved_property_group_missing_property_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "foo": { + "minimum": 0, + "multipleOf": 2 + } + } + })JSON")}; + + const sourcemeta::core::JSON instance{sourcemeta::core::parse_json(R"JSON({ + "bar": 1 + })JSON")}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 0, ""); +} + +TEST(Evaluator_draft7, resolved_property_group_non_object_instance_fast) { + const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "foo": { + "minimum": 0, + "multipleOf": 2 + } + } + })JSON")}; + + const sourcemeta::core::JSON instance{"nope"}; + + EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 0, ""); +} diff --git a/vendor/core/src/core/json/include/sourcemeta/core/json_object.h b/vendor/core/src/core/json/include/sourcemeta/core/json_object.h index 79eb8795c..aa0f1fd4d 100644 --- a/vendor/core/src/core/json/include/sourcemeta/core/json_object.h +++ b/vendor/core/src/core/json/include/sourcemeta/core/json_object.h @@ -438,16 +438,29 @@ template class JSONObject { const auto object_size{this->size()}; assert(start <= object_size); if (this->hasher.is_perfect(key_hash)) { - for (size_type count = 0; count < object_size; count++) { - const auto index{(start + count) % object_size}; + for (size_type index = start; index < object_size; index++) { + if (this->data[index].hash == key_hash) { + start = index + 1; + return &this->data[index].second; + } + } + + for (size_type index = 0; index < start; index++) { if (this->data[index].hash == key_hash) { start = index + 1; return &this->data[index].second; } } } else { - for (size_type count = 0; count < object_size; count++) { - const auto index{(start + count) % object_size}; + for (size_type index = start; index < object_size; index++) { + if (this->data[index].hash == key_hash && + this->data[index].first == key) { + start = index + 1; + return &this->data[index].second; + } + } + + for (size_type index = 0; index < start; index++) { if (this->data[index].hash == key_hash && this->data[index].first == key) { start = index + 1; @@ -470,16 +483,29 @@ template class JSONObject { const auto object_size{this->size()}; assert(start <= object_size); if (this->hasher.is_perfect(key_hash)) { - for (size_type count = 0; count < object_size; count++) { - const auto index{(start + count) % object_size}; + for (size_type index = start; index < object_size; index++) { + if (this->data[index].hash == key_hash) { + start = index + 1; + return &this->data[index].second; + } + } + + for (size_type index = 0; index < start; index++) { if (this->data[index].hash == key_hash) { start = index + 1; return &this->data[index].second; } } } else { - for (size_type count = 0; count < object_size; count++) { - const auto index{(start + count) % object_size}; + for (size_type index = start; index < object_size; index++) { + if (this->data[index].hash == key_hash && + this->data[index].first == key) { + start = index + 1; + return &this->data[index].second; + } + } + + for (size_type index = 0; index < start; index++) { if (this->data[index].hash == key_hash && this->data[index].first == key) { start = index + 1;