dry-validation 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.travis.yml +3 -2
  4. data/CHANGELOG.md +42 -0
  5. data/Gemfile +8 -1
  6. data/README.md +13 -89
  7. data/config/errors.yml +35 -29
  8. data/dry-validation.gemspec +2 -2
  9. data/examples/basic.rb +3 -7
  10. data/examples/each.rb +3 -8
  11. data/examples/form.rb +3 -6
  12. data/examples/nested.rb +7 -15
  13. data/lib/dry/validation.rb +33 -5
  14. data/lib/dry/validation/error.rb +10 -26
  15. data/lib/dry/validation/error_compiler.rb +69 -99
  16. data/lib/dry/validation/error_compiler/input.rb +148 -0
  17. data/lib/dry/validation/hint_compiler.rb +83 -33
  18. data/lib/dry/validation/input_processor_compiler.rb +98 -0
  19. data/lib/dry/validation/input_processor_compiler/form.rb +46 -0
  20. data/lib/dry/validation/input_processor_compiler/sanitizer.rb +46 -0
  21. data/lib/dry/validation/messages/abstract.rb +30 -10
  22. data/lib/dry/validation/messages/i18n.rb +2 -1
  23. data/lib/dry/validation/messages/namespaced.rb +1 -0
  24. data/lib/dry/validation/messages/yaml.rb +8 -5
  25. data/lib/dry/validation/result.rb +33 -25
  26. data/lib/dry/validation/schema.rb +168 -61
  27. data/lib/dry/validation/schema/attr.rb +5 -27
  28. data/lib/dry/validation/schema/check.rb +24 -0
  29. data/lib/dry/validation/schema/dsl.rb +97 -0
  30. data/lib/dry/validation/schema/form.rb +2 -26
  31. data/lib/dry/validation/schema/key.rb +32 -28
  32. data/lib/dry/validation/schema/rule.rb +88 -32
  33. data/lib/dry/validation/schema/value.rb +77 -27
  34. data/lib/dry/validation/schema_compiler.rb +38 -0
  35. data/lib/dry/validation/version.rb +1 -1
  36. data/spec/fixtures/locales/pl.yml +1 -1
  37. data/spec/integration/attr_spec.rb +122 -0
  38. data/spec/integration/custom_error_messages_spec.rb +9 -11
  39. data/spec/integration/custom_predicates_spec.rb +68 -18
  40. data/spec/integration/error_compiler_spec.rb +259 -65
  41. data/spec/integration/hints_spec.rb +28 -9
  42. data/spec/integration/injecting_rules_spec.rb +11 -12
  43. data/spec/integration/localized_error_messages_spec.rb +16 -16
  44. data/spec/integration/messages/i18n_spec.rb +9 -5
  45. data/spec/integration/optional_keys_spec.rb +9 -11
  46. data/spec/integration/schema/array_schema_spec.rb +23 -0
  47. data/spec/integration/schema/check_rules_spec.rb +39 -31
  48. data/spec/integration/schema/check_with_nth_el_spec.rb +25 -0
  49. data/spec/integration/schema/each_with_set_spec.rb +23 -24
  50. data/spec/integration/schema/form_spec.rb +122 -0
  51. data/spec/integration/schema/inheriting_schema_spec.rb +31 -0
  52. data/spec/integration/schema/input_processor_spec.rb +46 -0
  53. data/spec/integration/schema/macros/confirmation_spec.rb +33 -0
  54. data/spec/integration/schema/macros/maybe_spec.rb +32 -0
  55. data/spec/integration/schema/macros/required_spec.rb +59 -0
  56. data/spec/integration/schema/macros/when_spec.rb +65 -0
  57. data/spec/integration/schema/nested_values_spec.rb +41 -0
  58. data/spec/integration/schema/not_spec.rb +14 -14
  59. data/spec/integration/schema/option_with_default_spec.rb +30 -0
  60. data/spec/integration/schema/reusing_schema_spec.rb +33 -0
  61. data/spec/integration/schema/using_types_spec.rb +29 -0
  62. data/spec/integration/schema/xor_spec.rb +17 -14
  63. data/spec/integration/schema_spec.rb +75 -245
  64. data/spec/shared/rule_compiler.rb +8 -0
  65. data/spec/spec_helper.rb +13 -0
  66. data/spec/unit/hint_compiler_spec.rb +10 -10
  67. data/spec/unit/{input_type_compiler_spec.rb → input_processor_compiler/form_spec.rb} +88 -73
  68. data/spec/unit/schema/key_spec.rb +33 -0
  69. data/spec/unit/schema/rule_spec.rb +7 -6
  70. data/spec/unit/schema/value_spec.rb +187 -54
  71. metadata +53 -31
  72. data/.rubocop.yml +0 -16
  73. data/.rubocop_todo.yml +0 -7
  74. data/lib/dry/validation/input_type_compiler.rb +0 -83
  75. data/lib/dry/validation/schema/definition.rb +0 -74
  76. data/lib/dry/validation/schema/result.rb +0 -68
  77. data/rakelib/rubocop.rake +0 -18
  78. data/spec/integration/rule_groups_spec.rb +0 -94
  79. data/spec/integration/schema/attrs_spec.rb +0 -38
  80. data/spec/integration/schema/default_key_behavior_spec.rb +0 -23
  81. data/spec/integration/schema/grouped_rules_spec.rb +0 -57
  82. data/spec/integration/schema/nested_spec.rb +0 -31
  83. data/spec/integration/schema_form_spec.rb +0 -97
@@ -0,0 +1,98 @@
1
+ require 'dry/types'
2
+ require 'dry/types/compiler'
3
+
4
+ module Dry
5
+ module Validation
6
+ class InputProcessorCompiler
7
+ attr_reader :type_compiler
8
+
9
+ DEFAULT_TYPE_NODE = [[:type, 'string']].freeze
10
+
11
+ def initialize
12
+ @type_compiler = Dry::Types::Compiler.new(Dry::Types)
13
+ end
14
+
15
+ def call(ast)
16
+ type_compiler.(hash_node(schema_ast(ast)))
17
+ end
18
+
19
+ def schema_ast(ast)
20
+ ast.map { |node| visit(node) }
21
+ end
22
+
23
+ def visit(node, *args)
24
+ send(:"visit_#{node[0]}", node[1], *args)
25
+ end
26
+
27
+ def visit_schema(node, *args)
28
+ hash_node(node.input_processor_ast(identifier))
29
+ end
30
+
31
+ def visit_or(node, *args)
32
+ left, right = node
33
+ [:sum, [visit(left, *args), visit(right, *args)]]
34
+ end
35
+
36
+ def visit_and(node, first = true)
37
+ if first
38
+ name, type = node.map { |n| visit(n, false) }.uniq
39
+ [:key, [name, type]]
40
+ else
41
+ result = node.map { |n| visit(n, first) }.uniq
42
+
43
+ if result.size == 1
44
+ result.first
45
+ else
46
+ (result - self.class::DEFAULT_TYPE_NODE).first
47
+ end
48
+ end
49
+ end
50
+
51
+ def visit_implication(node)
52
+ key, types = node
53
+ [:key, [visit(key), visit(types, false)]]
54
+ end
55
+
56
+ def visit_key(node, *args)
57
+ _, other = node
58
+ visit(other, *args)
59
+ end
60
+
61
+ def visit_val(node, *args)
62
+ visit(node, *args)
63
+ end
64
+
65
+ def visit_set(node, *)
66
+ hash_node(node.map { |n| visit(n) })
67
+ end
68
+
69
+ def visit_each(node, *args)
70
+ array_node(visit(node, *args))
71
+ end
72
+
73
+ def visit_predicate(node, *args)
74
+ id, args = node
75
+
76
+ if id == :key?
77
+ args[0]
78
+ else
79
+ type(id, args)
80
+ end
81
+ end
82
+
83
+ def type(predicate, args)
84
+ default = self.class::PREDICATE_MAP[:default]
85
+
86
+ if predicate == :type?
87
+ const = args[0]
88
+ [:type, self.class::CONST_MAP[const] || Types.identifier(const)]
89
+ else
90
+ [:type, self.class::PREDICATE_MAP[predicate] || default]
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ require 'dry/validation/input_processor_compiler/sanitizer'
98
+ require 'dry/validation/input_processor_compiler/form'
@@ -0,0 +1,46 @@
1
+ module Dry
2
+ module Validation
3
+ class InputProcessorCompiler::Form < InputProcessorCompiler
4
+ PREDICATE_MAP = {
5
+ default: 'string',
6
+ none?: 'form.nil',
7
+ bool?: 'form.bool',
8
+ str?: 'string',
9
+ int?: 'form.int',
10
+ float?: 'form.float',
11
+ decimal?: 'form.decimal',
12
+ date?: 'form.date',
13
+ date_time?: 'form.date_time',
14
+ time?: 'form.time'
15
+ }.freeze
16
+
17
+ CONST_MAP = {
18
+ NilClass => 'form.nil',
19
+ String => 'string',
20
+ Fixnum => 'form.int',
21
+ Integer => 'form.int',
22
+ Float => 'form.float',
23
+ BigDecimal => 'form.decimal',
24
+ Array => 'form.array',
25
+ Hash => 'form.hash',
26
+ Date => 'form.date',
27
+ DateTime => 'form.date_time',
28
+ Time => 'form.time',
29
+ TrueClass => 'form.true',
30
+ FalseClass => 'form.false'
31
+ }.freeze
32
+
33
+ def identifier
34
+ :form
35
+ end
36
+
37
+ def hash_node(schema)
38
+ [:type, ['form.hash', [:symbolized, schema]]]
39
+ end
40
+
41
+ def array_node(members)
42
+ [:type, ['form.array', members]]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ module Dry
2
+ module Validation
3
+ class InputProcessorCompiler::Sanitizer < InputProcessorCompiler
4
+ PREDICATE_MAP = {
5
+ default: 'string',
6
+ none?: 'nil',
7
+ bool?: 'bool',
8
+ str?: 'string',
9
+ int?: 'int',
10
+ float?: 'float',
11
+ decimal?: 'decimal',
12
+ date?: 'date',
13
+ date_time?: 'date_time',
14
+ time?: 'time'
15
+ }.freeze
16
+
17
+ CONST_MAP = {
18
+ NilClass => 'nil',
19
+ String => 'string',
20
+ Fixnum => 'int',
21
+ Integer => 'int',
22
+ Float => 'float',
23
+ BigDecimal => 'decimal',
24
+ Array => 'array',
25
+ Hash => 'hash',
26
+ Date => 'date',
27
+ DateTime => 'date_time',
28
+ Time => 'time',
29
+ TrueClass => 'true',
30
+ FalseClass => 'false'
31
+ }.freeze
32
+
33
+ def identifier
34
+ :sanitizer
35
+ end
36
+
37
+ def hash_node(schema)
38
+ [:type, ['hash', [:schema, schema]]]
39
+ end
40
+
41
+ def array_node(members)
42
+ [:type, ['array', members]]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -5,9 +5,10 @@ module Dry
5
5
  module Validation
6
6
  module Messages
7
7
  class Abstract
8
- DEFAULT_PATH = Pathname(__dir__).join('../../../../config/errors.yml').realpath.freeze
9
-
10
8
  extend Dry::Configurable
9
+ include Dry::Equalizer(:config)
10
+
11
+ DEFAULT_PATH = Pathname(__dir__).join('../../../../config/errors.yml').realpath.freeze
11
12
 
12
13
  setting :paths, [DEFAULT_PATH]
13
14
  setting :root, 'errors'.freeze
@@ -34,12 +35,30 @@ module Dry
34
35
  String => 'string'
35
36
  )
36
37
 
38
+ def self.cache
39
+ @cache ||= ThreadSafe::Cache.new { |h, k| h[k] = ThreadSafe::Cache.new }
40
+ end
41
+
42
+ attr_reader :config
43
+
44
+ def initialize
45
+ @config = self.class.config
46
+ end
47
+
48
+ def rule(name, options = {})
49
+ path = "%{locale}.rules.#{name}"
50
+ get(path, options) if key?(path, options)
51
+ end
52
+
37
53
  def call(*args)
38
- cache.fetch_or_store(args.hash) { get(*lookup(*args)) }
54
+ cache.fetch_or_store(args.hash) do
55
+ path, opts = lookup(*args)
56
+ get(path, opts) if path
57
+ end
39
58
  end
40
59
  alias_method :[], :call
41
60
 
42
- def lookup(predicate, options)
61
+ def lookup(predicate, options = {})
43
62
  tokens = options.merge(
44
63
  root: root,
45
64
  predicate: predicate,
@@ -47,8 +66,13 @@ module Dry
47
66
  val_type: config.val_types[options[:val_type]]
48
67
  )
49
68
 
69
+ tokens[:rule] = predicate unless tokens.key?(:rule)
70
+
50
71
  opts = options.reject { |k, _| config.lookup_options.include?(k) }
51
- path = lookup_paths(tokens).detect { |key| key?(key, opts) && get(key).is_a?(String) }
72
+
73
+ path = lookup_paths(tokens).detect do |key|
74
+ key?(key, opts) && get(key, opts).is_a?(String)
75
+ end
52
76
 
53
77
  [path, opts]
54
78
  end
@@ -65,12 +89,8 @@ module Dry
65
89
  config.root
66
90
  end
67
91
 
68
- def config
69
- self.class.config
70
- end
71
-
72
92
  def cache
73
- @cache ||= ThreadSafe::Cache.new
93
+ self.class.cache[self]
74
94
  end
75
95
  end
76
96
  end
@@ -9,11 +9,12 @@ module Dry
9
9
  ::I18n.load_path.concat(config.paths)
10
10
 
11
11
  def initialize
12
+ super
12
13
  @t = I18n.method(:t)
13
14
  end
14
15
 
15
16
  def get(key, options = {})
16
- t.(key, options)
17
+ t.(key, options) if key
17
18
  end
18
19
 
19
20
  def key?(key, options)
@@ -5,6 +5,7 @@ module Dry
5
5
  attr_reader :namespace, :messages, :root
6
6
 
7
7
  def initialize(namespace, messages)
8
+ super()
8
9
  @namespace = namespace
9
10
  @messages = messages
10
11
  @root = messages.root
@@ -6,10 +6,12 @@ require 'dry/validation/messages/abstract'
6
6
  module Dry
7
7
  module Validation
8
8
  class Messages::YAML < Messages::Abstract
9
+ include Dry::Equalizer(:data)
10
+
9
11
  attr_reader :data
10
12
 
11
13
  configure do |config|
12
- config.root = 'en.errors'.freeze
14
+ config.root = '%{locale}.errors'.freeze
13
15
  end
14
16
 
15
17
  def self.load(paths = config.paths)
@@ -27,15 +29,16 @@ module Dry
27
29
  end
28
30
 
29
31
  def initialize(data)
32
+ super()
30
33
  @data = data
31
34
  end
32
35
 
33
- def get(key, _options = {})
34
- data[key]
36
+ def get(key, options = {})
37
+ data[key % { locale: options.fetch(:locale, :en) }]
35
38
  end
36
39
 
37
- def key?(key, *args)
38
- data.key?(key)
40
+ def key?(key, options = {})
41
+ data.key?(key % { locale: options.fetch(:locale, :en) })
39
42
  end
40
43
 
41
44
  def merge(overrides)
@@ -1,53 +1,61 @@
1
1
  module Dry
2
2
  module Validation
3
3
  class Result
4
+ include Dry::Equalizer(:output, :messages)
4
5
  include Enumerable
5
6
 
6
- attr_reader :rule_results
7
+ attr_reader :output
8
+ attr_reader :errors
9
+ attr_reader :error_compiler
10
+ attr_reader :hint_compiler
7
11
 
8
- def initialize(rule_results)
9
- @rule_results = rule_results
12
+ alias_method :to_hash, :output
13
+
14
+ EMPTY_MESSAGES = {}.freeze
15
+
16
+ def initialize(output, errors, error_compiler, hint_compiler)
17
+ @output = output
18
+ @errors = errors
19
+ @error_compiler = error_compiler
20
+ @hint_compiler = hint_compiler
10
21
  end
11
22
 
12
23
  def each(&block)
13
- rule_results.each(&block)
24
+ output.each(&block)
14
25
  end
15
26
 
16
27
  def [](name)
17
- to_h[name]
28
+ output.fetch(name)
18
29
  end
19
30
 
20
- def to_h
21
- @to_h ||= each_with_object({}) { |result, hash| hash[result.name] = result }
31
+ def success?
32
+ errors.empty?
22
33
  end
23
34
 
24
- def merge!(other)
25
- rule_results.concat(other.rule_results)
35
+ def failure?
36
+ !success?
26
37
  end
27
38
 
28
- def to_ary
29
- failures.map(&:to_ary)
30
- end
39
+ def messages(options = {})
40
+ @messages ||=
41
+ begin
42
+ return EMPTY_MESSAGES if success?
31
43
 
32
- def <<(rule_result)
33
- rule_results << rule_result
34
- end
44
+ hints = hint_compiler.with(options).call
45
+ comp = error_compiler.with(options.merge(hints: hints))
35
46
 
36
- def with_values(names, &block)
37
- values = names.map { |name| by_name(name) }.compact.map(&:input)
38
- yield(values) if values.size == names.size
47
+ comp.(error_ast)
48
+ end
39
49
  end
40
50
 
41
- def by_name(name)
42
- successes.detect { |rule_result| rule_result.name == name }
51
+ def to_ast
52
+ [:set, error_ast]
43
53
  end
44
54
 
45
- def successes
46
- rule_results.select(&:success?)
47
- end
55
+ private
48
56
 
49
- def failures
50
- rule_results.select(&:failure?)
57
+ def error_ast
58
+ errors.map { |error| error.to_ast }
51
59
  end
52
60
  end
53
61
  end
@@ -1,45 +1,73 @@
1
- require 'dry/logic/predicates'
2
- require 'dry/logic/rule_compiler'
1
+ require 'dry/types/constraints'
2
+
3
+ require 'dry/validation/schema_compiler'
4
+ require 'dry/validation/schema/key'
5
+ require 'dry/validation/schema/attr'
6
+ require 'dry/validation/schema/value'
7
+ require 'dry/validation/schema/check'
3
8
 
4
- require 'dry/validation/schema/definition'
5
9
  require 'dry/validation/error'
10
+ require 'dry/validation/result'
6
11
  require 'dry/validation/messages'
7
12
  require 'dry/validation/error_compiler'
8
13
  require 'dry/validation/hint_compiler'
9
- require 'dry/validation/result'
10
- require 'dry/validation/schema/result'
14
+
15
+ require 'dry/validation/input_processor_compiler'
11
16
 
12
17
  module Dry
13
18
  module Validation
14
19
  class Schema
15
20
  extend Dry::Configurable
16
- extend Definition
17
21
 
18
- setting :predicates, Logic::Predicates
22
+ NOOP_INPUT_PROCESSOR = -> input { input }
23
+
24
+ setting :path
25
+ setting :predicates, Types::Predicates
19
26
  setting :messages, :yaml
20
27
  setting :messages_file
21
28
  setting :namespace
29
+ setting :rules, []
30
+ setting :checks, []
22
31
 
23
- def self.predicates
24
- config.predicates
32
+ setting :input_processor, :noop
33
+
34
+ setting :input_processor_map, {
35
+ sanitizer: InputProcessorCompiler::Sanitizer.new,
36
+ form: InputProcessorCompiler::Form.new
37
+ }.freeze
38
+
39
+ def self.inherited(klass)
40
+ super
41
+ klass.setting :options, {}
25
42
  end
26
43
 
27
- def self.error_compiler
28
- ErrorCompiler.new(messages)
44
+ def self.new(rules = config.rules, **options)
45
+ super(rules, default_options.merge(options))
29
46
  end
30
47
 
31
- def self.hint_compiler
32
- HintCompiler.new(messages, rules: rules.map(&:to_ary))
48
+ def self.option(name, default = nil)
49
+ attr_reader(*name)
50
+ options.update(name => default)
51
+ end
52
+
53
+ def self.to_ast
54
+ [:schema, self]
55
+ end
56
+
57
+ def self.rules
58
+ config.rules
59
+ end
60
+
61
+ def self.predicates
62
+ config.predicates
63
+ end
64
+
65
+ def self.options
66
+ config.options
33
67
  end
34
68
 
35
69
  def self.messages
36
- default =
37
- case config.messages
38
- when :yaml then Messages.default
39
- when :i18n then Messages::I18n.new
40
- else
41
- fail "+#{config.messages}+ is not a valid messages identifier"
42
- end
70
+ default = default_messages
43
71
 
44
72
  if config.messages_file && config.namespace
45
73
  default.merge(config.messages_file).namespaced(config.namespace)
@@ -52,23 +80,61 @@ module Dry
52
80
  end
53
81
  end
54
82
 
55
- def self.rules
56
- @__rules__ ||= []
83
+ def self.default_messages
84
+ case config.messages
85
+ when :yaml then Messages.default
86
+ when :i18n then Messages::I18n.new
87
+ else
88
+ raise "+#{config.messages}+ is not a valid messages identifier"
89
+ end
90
+ end
91
+
92
+ def self.error_compiler
93
+ @error_compiler ||= ErrorCompiler.new(messages)
94
+ end
95
+
96
+ def self.hint_compiler
97
+ @hint_compiler ||= HintCompiler.new(messages, rules: rule_ast)
98
+ end
99
+
100
+ def self.input_processor
101
+ @input_processor ||=
102
+ begin
103
+ if input_processor_compiler
104
+ input_processor_compiler.(rule_ast)
105
+ else
106
+ NOOP_INPUT_PROCESSOR
107
+ end
108
+ end
109
+ end
110
+
111
+ def self.input_processor_ast(type)
112
+ config.input_processor_map.fetch(type).schema_ast(rule_ast)
57
113
  end
58
114
 
59
- def self.schemas
60
- @__schemas__ ||= []
115
+ def self.input_processor_compiler
116
+ @input_processor_comp ||= config.input_processor_map[config.input_processor]
61
117
  end
62
118
 
63
- def self.groups
64
- @__groups__ ||= []
119
+ def self.rule_ast
120
+ @rule_ast ||= config.rules.flat_map(&:rules).map(&:to_ast)
65
121
  end
66
122
 
67
- def self.checks
68
- @__checks__ ||= []
123
+ def self.default_options
124
+ { predicates: predicates,
125
+ error_compiler: error_compiler,
126
+ hint_compiler: hint_compiler,
127
+ input_processor: input_processor,
128
+ checks: config.checks }
69
129
  end
70
130
 
71
- attr_reader :rules, :schemas, :groups, :checks
131
+ attr_reader :rules
132
+
133
+ attr_reader :checks
134
+
135
+ attr_reader :predicates
136
+
137
+ attr_reader :input_processor
72
138
 
73
139
  attr_reader :rule_compiler
74
140
 
@@ -76,55 +142,96 @@ module Dry
76
142
 
77
143
  attr_reader :hint_compiler
78
144
 
79
- def initialize(rules = [])
80
- @rule_compiler = Logic::RuleCompiler.new(self)
81
- @rules = rule_compiler.(self.class.rules.map(&:to_ary) + rules.map(&:to_ary))
82
- @checks = self.class.checks.map(&:to_ary)
83
- @groups = rule_compiler.(self.class.groups.map(&:to_ary))
84
- @schemas = self.class.schemas.map(&:new)
85
- @error_compiler = self.class.error_compiler
86
- @hint_compiler = self.class.hint_compiler
145
+ attr_reader :options
146
+
147
+ def initialize(rules, options)
148
+ @rule_compiler = SchemaCompiler.new(self)
149
+ @error_compiler = options.fetch(:error_compiler)
150
+ @hint_compiler = options.fetch(:hint_compiler)
151
+ @predicates = options.fetch(:predicates)
152
+ @input_processor = options.fetch(:input_processor, NOOP_INPUT_PROCESSOR)
153
+
154
+ initialize_options(options)
155
+ initialize_rules(rules)
156
+ initialize_checks(options.fetch(:checks, []))
157
+
158
+ freeze
159
+ end
160
+
161
+ def with(new_options)
162
+ self.class.new(self.class.rules, options.merge(new_options))
87
163
  end
88
164
 
89
165
  def call(input)
90
- result = Validation::Result.new(rules.map { |rule| rule.(input) })
166
+ processed_input = input_processor[input]
167
+ Result.new(processed_input, apply(processed_input), error_compiler, hint_compiler)
168
+ end
91
169
 
92
- schemas.each do |schema|
93
- result.merge!(schema.(input).result)
170
+ def [](name)
171
+ if predicates.key?(name)
172
+ predicates[name]
173
+ elsif respond_to?(name)
174
+ Logic::Predicate.new(name, &method(name))
175
+ else
176
+ raise ArgumentError, "+#{name}+ is not a valid predicate name"
94
177
  end
178
+ end
95
179
 
96
- if checks.size > 0
97
- resolver = -> name { result[name] || self[name] }
98
- compiled_checks = Logic::RuleCompiler.new(resolver).(checks)
180
+ private
99
181
 
100
- compiled_checks.each do |rule|
101
- result << rule.(result)
102
- end
182
+ def apply(input)
183
+ results = rule_results(input)
184
+
185
+ results.merge!(check_results(input, results)) unless checks.empty?
186
+
187
+ results
188
+ .select { |_, result| result.failure? }
189
+ .map { |name, result| Error.new(error_path(name), result) }
190
+ end
191
+
192
+ def error_path(name)
193
+ full_path = Array[*self.class.config.path]
194
+ full_path << name
195
+ full_path.size > 1 ? full_path : full_path[0]
196
+ end
197
+
198
+ def rule_results(input)
199
+ rules.each_with_object({}) do |(name, rule), hash|
200
+ hash[name] = rule.(input)
103
201
  end
202
+ end
104
203
 
105
- groups.each do |group|
106
- result.with_values(group.rules) do |values|
107
- result << group.(*values)
108
- end
204
+ def check_results(input, result)
205
+ checks.each_with_object({}) do |(name, check), hash|
206
+ check_res = check.is_a?(Guard) ? check.(input, result) : check.(input)
207
+ hash[name] = check_res if check_res
109
208
  end
209
+ end
210
+
211
+ def initialize_options(options)
212
+ @options = options
110
213
 
111
- errors = Error::Set.new(result.failures.map { |failure| Error.new(failure) })
214
+ self.class.options.each do |name, default|
215
+ value = options.fetch(name) do
216
+ case default
217
+ when Proc then default.()
218
+ else default end
219
+ end
112
220
 
113
- Schema::Result.new(input, result, errors, error_compiler, hint_compiler)
221
+ instance_variable_set("@#{name}", value)
222
+ end
114
223
  end
115
224
 
116
- def [](name)
117
- if predicates.key?(name)
118
- predicates[name]
119
- elsif respond_to?(name)
120
- Logic::Predicate.new(name, &method(name))
121
- else
122
- fail ArgumentError, "+#{name}+ is not a valid predicate name"
225
+ def initialize_rules(rules)
226
+ @rules = rules.each_with_object({}) do |rule, result|
227
+ result[rule.name] = rule_compiler.visit(rule.to_ast)
123
228
  end
124
229
  end
125
230
 
126
- def predicates
127
- self.class.predicates
231
+ def initialize_checks(checks)
232
+ @checks = checks.each_with_object({}) do |check, result|
233
+ result[check.name] = rule_compiler.visit(check.to_ast)
234
+ end
128
235
  end
129
236
  end
130
237
  end