diff --git a/CHANGELOG.md b/CHANGELOG.md index ce37e97..567f233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * Count Unicode code points for `minLength` and `maxLength` without allocating a charlist for inputs larger than 256 bytes; retain standard conversion for inputs up to 256 bytes. Malformed UTF-8 now returns a validation error with `error_detail: "invalid_utf8"` instead of raising `UnicodeConversionError`. * Compile each `patternProperties` regex once when the same schema also uses `additionalProperties`, with a bounded per-schema cache that preserves vocabulary gates, error behavior, and validation semantics. - * Reuse runtime pattern-match classification between sibling `patternProperties` and `additionalProperties` rules within one validation invocation, reducing repeated regex matching for wide objects while preserving rule order, annotations, and diagnostics. + * Reuse runtime pattern-match classification between sibling `patternProperties` and `additionalProperties` rules within one validation invocation, while restricting cache-aware two-rule dispatch to those exact sibling pairs so ordinary two-rule schemas retain their direct fast path. * Adaptively merge evaluated keys from parent-dominated `anyOf` branches with balanced `MapSet` unions, avoiding repeated conversion of shared incoming evaluated keys while preserving full branch traversal, evaluated-key inputs, diagnostics, and legacy list-key behavior. * Avoid rebuilding already-flat validation error lists at the public validation boundary, and fuse nested error flattening with legacy error formatting while preserving diagnostic order, values, and internal error grouping. * Remove redundant input-key presence checks when validating `required`, while preserving declaration order, duplicate entries, non-object behavior, and diagnostic shape. diff --git a/lib/jsonschex/validator.ex b/lib/jsonschex/validator.ex index 76d482d..9e57ecc 100644 --- a/lib/jsonschex/validator.ex +++ b/lib/jsonschex/validator.ex @@ -130,21 +130,50 @@ defmodule JSONSchex.Validator do end end + # Restrict cache eligibility at function-clause dispatch so ordinary two-rule + # schemas do not pay for a runtime pattern-keyword classification call. Exact + # heads keep the ordinary fallback free from a compound guard evaluation. def validate_entry( - %Schema{rules: [rule1, rule2] = rules, source_id: nil}, + %Schema{ + rules: [ + %Rule{name: :patternProperties} = first_rule, + %Rule{name: :additionalProperties} = second_rule + ] = rules, + source_id: nil + }, data, path, context, evaluated ) do - ctx = {path, evaluated, context} + validate_two_pattern_rules(rules, first_rule, second_rule, data, path, context, evaluated) + end - case two_rule_pattern_match_cache(rule1, rule2, data) do - nil -> - run_two_rules(rule1, rule2, data, path, context, evaluated, ctx) - cache -> - run_rules_with_pattern_cache(rules, data, path, context, evaluated, ctx, [], cache) - end + def validate_entry( + %Schema{ + rules: [ + %Rule{name: :additionalProperties} = first_rule, + %Rule{name: :patternProperties} = second_rule + ] = rules, + source_id: nil + }, + data, + path, + context, + evaluated + ) do + validate_two_pattern_rules(rules, first_rule, second_rule, data, path, context, evaluated) + end + + def validate_entry( + %Schema{rules: [rule1, rule2], source_id: nil}, + data, + path, + context, + evaluated + ) do + ctx = {path, evaluated, context} + run_two_rules(rule1, rule2, data, path, context, evaluated, ctx) end def validate_entry( @@ -175,6 +204,18 @@ defmodule JSONSchex.Validator do defp to_error_entry(err_ctx, path, rule_name, data) when is_map(err_ctx), do: [%Error{path: path, rule: rule_name, context: err_ctx, value: data}] + defp validate_two_pattern_rules(rules, first_rule, second_rule, data, path, context, evaluated) do + ctx = {path, evaluated, context} + + case pattern_match_cache(rules, data) do + nil -> + run_two_rules(first_rule, second_rule, data, path, context, evaluated, ctx) + + cache -> + run_rules_with_pattern_cache(rules, data, path, context, evaluated, ctx, [], cache) + end + end + # Keep the specialized two-rule execution path for ordinary schemas. Only a # direct pair of sibling object keywords enters the cache-aware runner. defp run_two_rules(rule1, rule2, data, path, context, evaluated, ctx) do @@ -337,18 +378,6 @@ defmodule JSONSchex.Validator do defp pattern_match_cache_complete?(_rule, _cache), do: false - defp two_rule_pattern_match_cache( - %Rule{name: first_name} = first_rule, - %Rule{name: second_name} = second_rule, - data - ) - when first_name in [:patternProperties, :additionalProperties] and - second_name in [:patternProperties, :additionalProperties] and - first_name != second_name do - pattern_match_cache([first_rule, second_rule], data) - end - - defp two_rule_pattern_match_cache(_first_rule, _second_rule, _data), do: nil # A cache map cannot repay its setup work when the two sibling rules would # repeat exactly one match check. All larger candidate workloads stay eligible.