Skip to content

Commit

Permalink
Make KeyValidator faster by sorting keys and using binary search algo…
Browse files Browse the repository at this point in the history
…rithm (Array#bsearch) - attempt #2
  • Loading branch information
radarek authored and flash-gordon committed Jan 6, 2025
1 parent e143710 commit 8ad1c89
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 7 deletions.
25 changes: 18 additions & 7 deletions lib/dry/schema/key_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def call(result)
input = result.to_h

input_paths = key_paths(input)
key_paths = key_map.to_dot_notation
key_paths = key_map.to_dot_notation.sort

input_paths.each do |path|
error_path = validate_path(key_paths, path)
Expand All @@ -40,20 +40,31 @@ def call(result)
def validate_path(key_paths, path)
if path[INDEX_REGEX]
key = path.gsub(INDEX_REGEX, BRACKETS)

if key_paths.none? { paths_match?(key, _1) }
if none_key_paths_match?(key_paths, key)
arr = path.gsub(INDEX_REGEX) { ".#{_1[1]}" }
arr.split(DOT).map { DIGIT_REGEX.match?(_1) ? Integer(_1, 10) : _1.to_sym }
end
elsif key_paths.none? { paths_match?(path, _1) }
elsif none_key_paths_match?(key_paths, path)
path
end
end

# @api private
def paths_match?(input_path, key_path)
residue = key_path.sub(input_path, "")
residue.empty? || residue.start_with?(DOT, BRACKETS)
def none_key_paths_match?(key_paths, path)
!any_key_paths_match?(key_paths, path)
end

# @api private
def any_key_paths_match?(key_paths, path)
find_path(key_paths, path, false) ||
find_path(key_paths, path + DOT, true) ||
find_path(key_paths, path + BRACKETS, true)
end

# @api private
def find_path(key_paths, path, prefix_match)
key = key_paths.bsearch { |key_path| key_path >= path }
prefix_match ? key&.start_with?(path) : key == path
end

# @api private
Expand Down
18 changes: 18 additions & 0 deletions spec/integration/schema/key_searching_algorithm_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

RSpec.describe Dry::Schema, "key searching algorithm" do
it "works properly with keys that are prefixes of other keys" do
schema = Dry::Schema.define do
config.validate_keys = true

required(:a).filled(:string)
required(:fooA).filled(:string)
required(:foo).array(:hash) do
required(:bar).filled(:string)
end
end

expect(schema.(a: "string", fooA: "string", foo: "string").errors.to_h)
.to eql({foo: ["must be an array"]})
end
end

0 comments on commit 8ad1c89

Please sign in to comment.