File: validators.rb

package info (click to toggle)
ruby-contracts 0.17-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 624 kB
  • sloc: ruby: 3,805; makefile: 4; sh: 2
file content (143 lines) | stat: -rw-r--r-- 3,555 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# frozen_string_literal: true

module Contracts
  module Validators
    DEFAULT_VALIDATOR_STRATEGIES = {
      # e.g. lambda {true}
      Proc => lambda { |contract| contract },

      # e.g. [Num, String]
      # TODO: account for these errors too
      Array => lambda do |contract|
        lambda do |arg|
          return false unless arg.is_a?(Array) && arg.length == contract.length

          arg.zip(contract).all? do |_arg, _contract|
            Contract.valid?(_arg, _contract)
          end
        end
      end,

      # e.g. { :a => Num, :b => String }
      Hash => lambda do |contract|
        lambda do |arg|
          return false unless arg.is_a?(Hash)

          contract.keys.all? do |k|
            Contract.valid?(arg[k], contract[k])
          end
        end
      end,

      Range => lambda do |contract|
        lambda do |arg|
          contract.include?(arg)
        end
      end,

      Regexp => lambda do |contract|
        lambda do |arg|
          arg =~ contract
        end
      end,

      Contracts::Args => lambda do |contract|
        lambda do |arg|
          Contract.valid?(arg, contract.contract)
        end
      end,

      Contracts::Func => lambda do |_|
        lambda do |arg|
          arg.is_a?(Method) || arg.is_a?(Proc)
        end
      end,

      :valid => lambda do |contract|
        lambda { |arg| contract.valid?(arg) }
      end,

      :class => lambda do |contract|
        lambda { |arg| arg.is_a?(contract) }
      end,

      :default => lambda do |contract|
        lambda { |arg| contract == arg }
      end,
    }.freeze

    # Allows to override validator with custom one.
    # Example:
    #   Contract.override_validator(Array) do |contract|
    #     lambda do |arg|
    #       # .. implementation for Array contract ..
    #     end
    #   end
    #
    #   Contract.override_validator(:class) do |contract|
    #     lambda do |arg|
    #       arg.is_a?(contract) || arg.is_a?(RSpec::Mocks::Double)
    #     end
    #   end
    def override_validator(name, &block)
      validator_strategies[name] = block
    end

    # This is a little weird. For each contract
    # we pre-make a proc to validate it so we
    # don't have to go through this decision tree every time.
    # Seems silly but it saves us a bunch of time (4.3sec vs 5.2sec)
    def make_validator!(contract)
      klass = contract.class
      key = if validator_strategies.key?(klass)
              klass
            else
              if contract.respond_to? :valid?
                :valid
              elsif [Class, Module].include?(klass)
                :class
              else
                :default
              end
            end

      validator_strategies[key].call(contract)
    end

    def make_validator(contract)
      contract_id = Support.contract_id(contract)

      if memoized_validators.key?(contract_id)
        return memoized_validators[contract_id]
      end

      memoized_validators[contract_id] = make_validator!(contract)
    end

    # @private
    def reset_validators
      clean_memoized_validators
      restore_validators
    end

    # @private
    def validator_strategies
      @_validator_strategies ||= restore_validators
    end

    # @private
    def restore_validators
      @_validator_strategies = DEFAULT_VALIDATOR_STRATEGIES.dup
    end

    # @private
    def memoized_validators
      @_memoized_validators ||= clean_memoized_validators
    end

    # @private
    def clean_memoized_validators
      @_memoized_validators = {}
    end
  end
end