dry-validation 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -1,35 +1,13 @@
1
1
  module Dry
2
2
  module Validation
3
3
  class Schema
4
- class Attr < BasicObject
5
- attr_reader :name, :rules
6
-
7
- def initialize(name, rules, &block)
8
- @name = name
9
- @rules = rules
10
- end
11
-
12
- private
13
-
14
- def method_missing(meth, *args, &block)
15
- attr_rule = [:attr, [name, [:predicate, [meth, args]]]]
16
-
17
- if block
18
- val_rule = yield(Value.new(name))
19
-
20
- rules <<
21
- if val_rule.is_a?(::Array)
22
- Schema::Rule.new(name, [:and, [attr_rule, [:set, [name, val_rule.map(&:to_ary)]]]])
23
- else
24
- Schema::Rule.new(name, [:and, [attr_rule, val_rule.to_ary]])
25
- end
26
- else
27
- Schema::Rule.new(name, attr_rule)
28
- end
4
+ class Attr < Key
5
+ def self.type
6
+ :attr
29
7
  end
30
8
 
31
- def respond_to_missing?(*)
32
- true
9
+ def class
10
+ Attr
33
11
  end
34
12
  end
35
13
  end
@@ -0,0 +1,24 @@
1
+ module Dry
2
+ module Validation
3
+ class Schema
4
+ class Check < Value
5
+ def class
6
+ Check
7
+ end
8
+
9
+ private
10
+
11
+ def method_missing(meth, *meth_args)
12
+ vals, args = meth_args.partition { |arg| arg.class < DSL }
13
+
14
+ keys = [name, *vals.map(&:name)]
15
+ predicate = [:predicate, [meth, args]]
16
+
17
+ rule = create_rule([:check, [name, predicate, keys]])
18
+ add_rule(rule)
19
+ rule
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,97 @@
1
+ require 'dry/validation/schema/rule'
2
+
3
+ module Dry
4
+ module Validation
5
+ class Schema
6
+ class DSL < BasicObject
7
+ attr_reader :name, :rules, :checks, :parent, :options
8
+
9
+ def self.[](name, options = {})
10
+ new(options.merge(name: name))
11
+ end
12
+
13
+ def initialize(options = {})
14
+ @name = options[:name]
15
+ @parent = options[:parent]
16
+ @rules = options.fetch(:rules, [])
17
+ @checks = options.fetch(:checks, [])
18
+ @options = options
19
+ end
20
+
21
+ def key(name, &block)
22
+ define(name, Key, &block)
23
+ end
24
+
25
+ def optional(name, &block)
26
+ define(name, Key, :then, &block)
27
+ end
28
+
29
+ def attr(name, &block)
30
+ define(name, Attr, &block)
31
+ end
32
+
33
+ def not
34
+ negated = create_rule([:not, to_ast])
35
+ @rules = [negated]
36
+ self
37
+ end
38
+
39
+ def add_rule(rule)
40
+ rules << rule
41
+ self
42
+ end
43
+
44
+ def add_check(check)
45
+ checks << check
46
+ self
47
+ end
48
+
49
+ def to_ast
50
+ ast = rules.map(&:to_ast)
51
+ ast.size > 1 ? [:set, ast] : ast[0] || []
52
+ end
53
+
54
+ def to_rule
55
+ create_rule(to_ast)
56
+ end
57
+
58
+ def path
59
+ items = [parent && parent.path, name].flatten.compact.uniq
60
+ items.size == 1 ? items[0] : items
61
+ end
62
+
63
+ def with(new_options)
64
+ self.class.new(options.merge(new_options))
65
+ end
66
+
67
+ private
68
+
69
+ def define(name, key_class, op = :and, &block)
70
+ type = key_class.type
71
+
72
+ val = Value[
73
+ name, type: type, parent: self, rules: rules, checks: checks
74
+ ].__send__(:"#{type}?", name)
75
+
76
+ if block
77
+ key = key_class[name]
78
+ res = key.instance_eval(&block)
79
+
80
+ if res.class == Value
81
+ checks.concat(key.checks)
82
+ add_rule(val.__send__(op, create_rule([type, [name, res.to_ast]])))
83
+ else
84
+ add_rule(val.__send__(op, create_rule(res.to_ast)))
85
+ end
86
+ else
87
+ val.with(type: op)
88
+ end
89
+ end
90
+
91
+ def create_rule(node)
92
+ Schema::Rule.new(node, name: name, target: self)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,34 +1,10 @@
1
1
  require 'dry/validation/schema'
2
- require 'dry/validation/input_type_compiler'
3
2
 
4
3
  module Dry
5
4
  module Validation
6
5
  class Schema::Form < Schema
7
- attr_reader :input_type
8
-
9
- def self.key(name, &block)
10
- if block
11
- super
12
- else
13
- super(name, &:filled?)
14
- end
15
- end
16
-
17
- def self.optional(name, &block)
18
- if block
19
- super
20
- else
21
- super(name, &:filled?)
22
- end
23
- end
24
-
25
- def initialize
26
- super
27
- @input_type = InputTypeCompiler.new.(self.class.rules.map(&:to_ary))
28
- end
29
-
30
- def call(input)
31
- super(input_type[input])
6
+ configure do |config|
7
+ config.input_processor = :form
32
8
  end
33
9
  end
34
10
  end
@@ -1,49 +1,53 @@
1
+ require 'dry/validation/schema/dsl'
2
+
1
3
  module Dry
2
4
  module Validation
3
5
  class Schema
4
- class Key < BasicObject
5
- attr_reader :name, :rules
6
+ class Key < DSL
7
+ attr_reader :parent
8
+
9
+ def self.type
10
+ :key
11
+ end
12
+
13
+ def class
14
+ Key
15
+ end
6
16
 
7
- def initialize(name, rules, &block)
8
- @name = name
9
- @rules = rules
17
+ def type
18
+ self.class.type
10
19
  end
11
20
 
12
- def optional(&block)
13
- key_rule = key?
21
+ def to_ast
22
+ [type, [name, super]]
23
+ end
24
+
25
+ def hash?(&block)
26
+ val = Value[name]
27
+ val.instance_eval(&block)
28
+
29
+ rule = create_rule([:val, [:predicate, [:hash?, []]]])
30
+ .and(create_rule([type, [name, [:set, val.rules.map(&:to_ast)]]]))
14
31
 
15
- val_rule = yield(Value.new(name))
32
+ add_rule(rule)
16
33
 
17
- rules <<
18
- if val_rule.is_a?(::Array)
19
- Schema::Rule.new(name, [:implication, [key_rule.to_ary, [:set, [name, val_rule.map(&:to_ary)]]]])
20
- else
21
- Schema::Rule.new(name, [:implication, [key_rule.to_ary, val_rule.to_ary]])
22
- end
34
+ rule
23
35
  end
24
36
 
25
37
  private
26
38
 
27
39
  def method_missing(meth, *args, &block)
28
- key_rule = [:key, [name, [:predicate, [meth, args]]]]
40
+ predicate = [:predicate, [meth, args]]
29
41
 
30
42
  if block
31
- val_rule = yield(Value.new(name))
32
-
33
- rules <<
34
- if val_rule.is_a?(::Array)
35
- Schema::Rule.new(name, [:and, [key_rule, [:set, [name, val_rule.map(&:to_ary)]]]])
36
- else
37
- Schema::Rule.new(name, [:and, [key_rule, val_rule.to_ary]])
38
- end
43
+ val = Value[name].instance_eval(&block)
44
+ add_rule(create_rule([:and, [[:val, predicate], val.to_ast]]))
39
45
  else
40
- Schema::Rule.new(name, key_rule)
46
+ rule = create_rule([type, [name, predicate]])
47
+ add_rule(rule)
48
+ rule
41
49
  end
42
50
  end
43
-
44
- def respond_to_missing?(*)
45
- true
46
- end
47
51
  end
48
52
  end
49
53
  end
@@ -2,73 +2,129 @@ module Dry
2
2
  module Validation
3
3
  class Schema
4
4
  class Rule < BasicObject
5
- attr_reader :name, :node
5
+ attr_reader :name, :node, :type, :target, :deps, :options
6
6
 
7
- class Check < Rule
8
- def class
9
- Schema::Rule::Check
10
- end
7
+ def initialize(node, options = {})
8
+ @node = node
9
+ @type = options.fetch(:type, :and)
10
+ @deps = options.fetch(:deps, [])
11
+ @name = options.fetch(:name)
12
+ @target = options.fetch(:target)
13
+ @options = options
14
+ end
11
15
 
12
- def method_missing(meth, *)
13
- self.class.new(name, [:check, [name, [:predicate, [name, [meth]]]]])
14
- end
16
+ def schema(other = nil, &block)
17
+ schema = other ? ::Class.new(other.class) : Validation.Schema(
18
+ target.schema_class, parent: target, build: false, &block
19
+ )
20
+
21
+ schema.config.path = [name] if other
22
+ schema.config.input_processor = :noop
23
+
24
+ rule = __send__(type, key(:hash?).and(key(schema)))
25
+
26
+ add_rule(rule)
15
27
  end
16
28
 
17
- class Result < Rule
18
- def class
19
- Schema::Rule::Result
20
- end
29
+ def required(*predicates)
30
+ rule = ([key(:filled?)] + infer_predicates(predicates)).reduce(:and)
21
31
 
22
- def method_missing(meth, *args)
23
- self.class.new(name, [:res, [name, [:predicate, [meth, args]]]])
24
- end
32
+ add_rule(__send__(type, rule))
25
33
  end
26
34
 
27
- def initialize(name, node)
28
- @name = name
29
- @node = node
35
+ def maybe(*predicates)
36
+ rule =
37
+ if predicates.size > 0
38
+ key(:none?).or(infer_predicates(predicates).reduce(:and))
39
+ else
40
+ key(:none?).or(key(:filled?))
41
+ end
42
+
43
+ add_rule(__send__(type, rule))
30
44
  end
31
45
 
32
- def class
33
- Schema::Rule
46
+ def each(*predicates, &block)
47
+ rule = target.each(*predicates, &block)
48
+ add_rule(__send__(type, new([target.type, [name, rule.to_ast]])))
49
+ end
50
+
51
+ def add_rule(rule)
52
+ target.add_rule(rule)
34
53
  end
35
54
 
36
- def to_ary
37
- node
55
+ def rules
56
+ target.rules
38
57
  end
39
- alias_method :to_a, :to_ary
40
58
 
41
- def to_check
42
- Rule::Check.new(name, [:check, [name, [:predicate, [name, []]]]])
59
+ def checks
60
+ target.checks
43
61
  end
44
62
 
45
- def is_a?(other)
46
- self.class == other
63
+ def to_ast
64
+ if deps.empty?
65
+ node
66
+ else
67
+ [:guard, [deps, node]]
68
+ end
69
+ end
70
+
71
+ def class
72
+ Schema::Rule
47
73
  end
48
74
 
49
75
  def not
50
- self.class.new(:"not_#{name}", [:not, node])
76
+ new([:not, node])
51
77
  end
52
78
 
53
79
  def and(other)
54
- self.class.new(:"#{name}_and_#{other.name}", [:and, [node, other.to_ary]])
80
+ new([:and, [node, other.to_ast]])
55
81
  end
56
82
  alias_method :&, :and
57
83
 
58
84
  def or(other)
59
- self.class.new(:"#{name}_or_#{other.name}", [:or, [node, other.to_ary]])
85
+ new([:or, [node, other.to_ast]])
60
86
  end
61
87
  alias_method :|, :or
62
88
 
63
89
  def xor(other)
64
- self.class.new(:"#{name}_xor_#{other.name}", [:xor, [node, other.to_ary]])
90
+ new([:xor, [node, other.to_ast]])
65
91
  end
66
92
  alias_method :^, :xor
67
93
 
68
94
  def then(other)
69
- self.class.new(:"#{name}_then_#{other.name}", [:implication, [node, other.to_ary]])
95
+ new([:implication, [node, other.to_ast]])
70
96
  end
71
97
  alias_method :>, :then
98
+
99
+ def infer_predicates(predicates)
100
+ predicates.map do |predicate|
101
+ name, *args = ::Kernel.Array(predicate).first
102
+ key(name, args)
103
+ end
104
+ end
105
+
106
+ def with(new_options)
107
+ self.class.new(node, options.merge(new_options))
108
+ end
109
+
110
+ private
111
+
112
+ def key(predicate, args = [])
113
+ node =
114
+ if predicate.is_a?(::Symbol)
115
+ [target.type, [name, [:predicate, [predicate, args]]]]
116
+ elsif predicate.is_a?(Types::Constrained)
117
+ [target.type, [name, predicate.rule.to_ast]]
118
+ else
119
+ [target.type, [name, predicate.to_ast]]
120
+ end
121
+
122
+ new(node)
123
+ end
124
+
125
+ def new(node)
126
+ self.class.new(node, options)
127
+ end
72
128
  end
73
129
  end
74
130
  end
@@ -1,52 +1,102 @@
1
+ require 'dry/validation/schema/dsl'
2
+
1
3
  module Dry
2
4
  module Validation
3
5
  class Schema
4
- class Value < BasicObject
5
- include Schema::Definition
6
+ class Value < DSL
7
+ attr_reader :type, :schema_class
6
8
 
7
- attr_reader :name, :rules, :groups, :checks
9
+ def initialize(options = {})
10
+ super
11
+ @type = options.fetch(:type, :key)
12
+ @schema_class = options.fetch(:schema_class, ::Class.new(Schema))
13
+ end
8
14
 
9
- def initialize(name)
10
- @name = name
11
- @rules = []
12
- @groups = []
13
- @checks = []
15
+ def configure(&block)
16
+ klass = ::Class.new(schema_class, &block)
17
+ @schema_class = klass
18
+ self
14
19
  end
15
20
 
16
- def each(&block)
17
- val_rule = yield(self)
21
+ def root?
22
+ name.nil?
23
+ end
18
24
 
19
- each_rule =
20
- if val_rule.is_a?(Schema::Rule)
21
- val_rule.to_ary
25
+ def class
26
+ Value
27
+ end
28
+
29
+ def each(*predicates, &block)
30
+ val =
31
+ if predicates.size > 0
32
+ predicates
33
+ .reduce(Value.new) { |a, e| a.__send__(*::Kernel.Array(e)) }
22
34
  else
23
- [:set, [name, rules.map(&:to_ary)]]
35
+ Value[name].instance_eval(&block)
24
36
  end
25
37
 
26
- Schema::Rule.new(name, [:each, [name, each_rule]])
38
+ rule = array?.and(create_rule([:each, val.to_ast]))
39
+
40
+ add_rule(rule) if root?
41
+
42
+ rule
43
+ end
44
+
45
+ def when(*predicates, &block)
46
+ left = predicates
47
+ .reduce(Check[path, type: type]) { |a, e| a.__send__(*::Kernel.Array(e)) }
48
+
49
+ right = Value.new(type: type)
50
+ right.instance_eval(&block)
51
+
52
+ add_check(left.then(create_rule(right.to_ast)))
53
+
54
+ self
55
+ end
56
+
57
+ def rule(id = nil, **options, &block)
58
+ if id
59
+ val = Value[id]
60
+ res = val.instance_exec(&block)
61
+ else
62
+ id, deps = options.to_a.first
63
+ val = Value[id]
64
+ res = val.instance_exec(*deps.map { |name| val.value(name) }, &block)
65
+ end
66
+
67
+ add_check(val.with(rules: [res.with(deps: deps || [])]))
68
+ end
69
+
70
+ def confirmation
71
+ rule = check(:"#{name}_confirmation")
72
+ .eql?(check(name))
73
+ .with(deps: [name])
74
+
75
+ add_check(rule)
76
+ end
77
+
78
+ def value(name)
79
+ check(name, rules: rules)
80
+ end
81
+
82
+ def check(name, options = {})
83
+ Check[name, options.merge(type: type)]
27
84
  end
28
85
 
29
86
  private
30
87
 
31
88
  def method_missing(meth, *args, &block)
32
- rule = Schema::Rule.new(name, [:val, [name, [:predicate, [meth, args]]]])
89
+ val_rule = create_rule([:val, [:predicate, [meth, args]]])
33
90
 
34
91
  if block
35
- val_rule = yield
92
+ val = Value.new.instance_eval(&block)
93
+ new_rule = create_rule([:and, [val_rule.to_ast, val.to_ast]])
36
94
 
37
- if val_rule.is_a?(Schema::Rule)
38
- rule & val_rule
39
- else
40
- Schema::Rule.new(name, [:and, [rule.to_ary, [:set, [name, rules.map(&:to_ary)]]]])
41
- end
95
+ add_rule(new_rule)
42
96
  else
43
- rule
97
+ val_rule
44
98
  end
45
99
  end
46
-
47
- def respond_to_missing?(meth, _include_private = false)
48
- true
49
- end
50
100
  end
51
101
  end
52
102
  end