class Speculation::HashSpec

@private

Constants

S

This is a Ruby translation of clojure.spec:

https://github.com/clojure/clojure/blob/master/src/clj/clojure/spec.clj

All credit belongs with Rich Hickey and contributors for their original work.


This is a Ruby translation of clojure.spec:

https://github.com/clojure/clojure/blob/master/src/clj/clojure/spec.clj

All credit belongs with Rich Hickey and contributors for their original work.


This is a Ruby translation of clojure.spec:

https://github.com/clojure/clojure/blob/master/src/clj/clojure/spec.clj

All credit belongs with Rich Hickey and contributors for their original work.


This is a Ruby translation of clojure.spec:

https://github.com/clojure/clojure/blob/master/src/clj/clojure/spec.clj

All credit belongs with Rich Hickey and contributors for their original work.

Attributes

id[R]

Public Class Methods

new(req, opt, req_un, opt_un, gen = nil) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 15
def initialize(req, opt, req_un, opt_un, gen = nil)
  @id = SecureRandom.uuid
  @req = req
  @opt = opt
  @req_un = req_un
  @opt_un = opt_un
  @gen = gen

  req_keys     = req.flat_map(&method(:extract_keys))
  req_un_specs = req_un.flat_map(&method(:extract_keys))

  all_keys = req_keys + req_un_specs + opt + opt_un
  unless all_keys.all? { |s| s.is_a?(Symbol) && NamespacedSymbols.namespace(s) }
    raise "all keys must be namespaced Symbols"
  end

  req_specs = req_keys + req_un_specs
  req_keys += req_un_specs.map(&method(:unqualify_key))

  pred_exprs = [Predicates.method(:hash?)]
  pred_exprs.push(->(v) { parse_req(req, v, Utils.method(:itself)).empty? }) if req.any?
  pred_exprs.push(->(v) { parse_req(req_un, v, method(:unqualify_key)).empty? }) if req_un.any?

  @req_keys        = req_keys
  @req_specs       = req_specs
  @opt_keys        = opt + opt_un.map(&method(:unqualify_key))
  @opt_specs       = opt + opt_un
  @keys_pred       = ->(v) { pred_exprs.all? { |p| p.call(v) } }
  @key_to_spec_map = Hash[Utils.conj(req_keys, @opt_keys).zip(Utils.conj(req_specs, @opt_specs))]
end

Public Instance Methods

conform(value) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 46
def conform(value)
  return :"Speculation/invalid" unless @keys_pred.call(value)

  reg = S.registry

  value.reduce(value) do |ret, (key, v)|
    spec_name = key_to_spec_name(key)
    spec = reg[spec_name]

    if spec
      conformed_value = S.conform(spec, v)

      if S.invalid?(conformed_value)
        break :"Speculation/invalid"
      elsif conformed_value.equal?(v)
        ret
      else
        ret.merge(key => conformed_value)
      end
    else
      ret
    end
  end
end
explain(path, via, inn, value) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 89
def explain(path, via, inn, value)
  unless Predicates.hash?(value)
    return [{ :path => path, :pred => [Predicates.method(:hash?), [value]], :val => value, :via => via, :in => inn }]
  end

  reg = S.registry
  problems = []

  if @req.any?
    failures = parse_req(@req, value, Utils.method(:itself))

    failures.each do |failure_sexp|
      pred = [Predicates.method(:key?), [failure_sexp]]
      problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
    end
  end

  if @req_un.any?
    failures = parse_req(@req_un, value, method(:unqualify_key))

    failures.each do |failure_sexp|
      pred = [Predicates.method(:key?), [failure_sexp]]
      problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
    end
  end

  problems += value.flat_map { |(k, v)|
    next unless reg.key?(key_to_spec_name(k))
    next if S.pvalid?(key_to_spec_name(k), v)

    S.explain1(key_to_spec_name(k), Utils.conj(path, k), via, Utils.conj(inn, k), v)
  }

  problems.compact
end
gen(overrides, path, rmap) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 129
def gen(overrides, path, rmap)
  return @gen.call if @gen

  rmap = S.inck(rmap, @id)

  reqs = @req_keys.zip(@req_specs).
    reduce({}) { |m, (k, s)|
      m.merge(k => S.gensub(s, overrides, Utils.conj(path, k), rmap))
    }

  opts = @opt_keys.zip(@opt_specs).
    reduce({}) { |m, (k, s)|
      if S.recur_limit?(rmap, @id, path, k)
        m
      else
        m.merge(k => Gen.delay { S.gensub(s, overrides, Utils.conj(path, k), rmap) })
      end
    }

  ->(rantly) do
    count = rantly.range(0, opts.count)
    opts = Hash[opts.to_a.shuffle.take(count)]

    reqs.merge(opts).each_with_object({}) { |(k, spec_gen), h|
      h[k] = spec_gen.call(rantly)
    }
  end
end
unform(value) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 71
def unform(value)
  reg = S.registry

  value.reduce(value) do |ret, (key, conformed_value)|
    if reg.key?(key_to_spec_name(key))
      unformed_value = S.unform(key_to_spec_name(key), conformed_value)

      if conformed_value.equal?(unformed_value)
        ret
      else
        ret.merge(key => unformed_value)
      end
    else
      ret
    end
  end
end
with_gen(gen) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 125
def with_gen(gen)
  self.class.new(@req, @opt, @req_un, @opt_un, gen)
end

Private Instance Methods

extract_keys(symbol_or_arr) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 160
def extract_keys(symbol_or_arr)
  if symbol_or_arr.is_a?(Array)
    symbol_or_arr[1..-1].flat_map(&method(:extract_keys))
  else
    symbol_or_arr
  end
end
key_to_spec_name(k) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 216
def key_to_spec_name(k)
  @key_to_spec_map.fetch(k, k)
end
parse_req(ks, v, f) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 172
def parse_req(ks, v, f)
  key, *ks = ks

  ret = if key.is_a?(Array)
          op, *kks = key
          case op
          when :"Speculation/or"
            if kks.any? { |k| parse_req([k], v, f).empty? }
              []
            else
              transform_keys([key], f)
            end
          when :"Speculation/and"
            if kks.all? { |k| parse_req([k], v, f).empty? }
              []
            else
              transform_keys([key], f)
            end
          else
            raise "Expected Speculation/or, Speculation/and, got #{op}"
          end
        elsif v.key?(f.call(key))
          []
        else
          [f.call(key)]
        end

  if ks.any?
    ret + parse_req(ks, v, f)
  else
    ret
  end
end
transform_keys(keys, f) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 206
def transform_keys(keys, f)
  keys.map { |key|
    case key
    when Array then transform_keys(key, f)
    when :"Speculation/and", :"Speculation/or" then key
    else f.call(key)
    end
  }
end
unqualify_key(x) click to toggle source
# File lib/speculation/spec/hash_spec.rb, line 168
def unqualify_key(x)
  NamespacedSymbols.namespaced_name(x).to_sym
end