dry-validation 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +23 -0
  4. data/README.md +203 -26
  5. data/config/errors.yml +16 -0
  6. data/dry-validation.gemspec +1 -0
  7. data/examples/each.rb +19 -0
  8. data/examples/form.rb +15 -0
  9. data/examples/nested.rb +13 -11
  10. data/lib/dry/validation.rb +5 -0
  11. data/lib/dry/validation/error_compiler.rb +2 -18
  12. data/lib/dry/validation/input_type_compiler.rb +78 -0
  13. data/lib/dry/validation/messages.rb +1 -7
  14. data/lib/dry/validation/predicates.rb +33 -1
  15. data/lib/dry/validation/result.rb +8 -0
  16. data/lib/dry/validation/rule.rb +11 -90
  17. data/lib/dry/validation/rule/composite.rb +50 -0
  18. data/lib/dry/validation/rule/each.rb +13 -0
  19. data/lib/dry/validation/rule/key.rb +17 -0
  20. data/lib/dry/validation/rule/set.rb +22 -0
  21. data/lib/dry/validation/rule/value.rb +13 -0
  22. data/lib/dry/validation/rule_compiler.rb +5 -0
  23. data/lib/dry/validation/schema.rb +6 -2
  24. data/lib/dry/validation/schema/definition.rb +4 -0
  25. data/lib/dry/validation/schema/form.rb +19 -0
  26. data/lib/dry/validation/schema/key.rb +16 -3
  27. data/lib/dry/validation/schema/result.rb +29 -0
  28. data/lib/dry/validation/schema/rule.rb +14 -16
  29. data/lib/dry/validation/schema/value.rb +14 -2
  30. data/lib/dry/validation/version.rb +1 -1
  31. data/spec/integration/custom_error_messages_spec.rb +1 -1
  32. data/spec/integration/optional_keys_spec.rb +30 -0
  33. data/spec/integration/schema_form_spec.rb +99 -0
  34. data/spec/integration/{validation_spec.rb → schema_spec.rb} +40 -13
  35. data/spec/shared/predicates.rb +1 -1
  36. data/spec/unit/error_compiler_spec.rb +64 -0
  37. data/spec/unit/input_type_compiler_spec.rb +205 -0
  38. data/spec/unit/predicates/bool_spec.rb +34 -0
  39. data/spec/unit/predicates/date_spec.rb +31 -0
  40. data/spec/unit/predicates/date_time_spec.rb +31 -0
  41. data/spec/unit/predicates/decimal_spec.rb +32 -0
  42. data/spec/unit/predicates/float_spec.rb +31 -0
  43. data/spec/unit/predicates/{nil_spec.rb → none_spec.rb} +2 -2
  44. data/spec/unit/predicates/time_spec.rb +31 -0
  45. data/spec/unit/rule/conjunction_spec.rb +28 -0
  46. data/spec/unit/rule/disjunction_spec.rb +36 -0
  47. data/spec/unit/rule/implication_spec.rb +14 -0
  48. data/spec/unit/rule/value_spec.rb +1 -1
  49. metadata +60 -6
@@ -0,0 +1,78 @@
1
+ require 'dry/data'
2
+ require 'dry/data/compiler'
3
+
4
+ module Dry
5
+ module Validation
6
+ class InputTypeCompiler
7
+ attr_reader :type_compiler
8
+
9
+ TYPES = {
10
+ default: 'string',
11
+ none?: 'form.nil',
12
+ bool?: 'form.bool',
13
+ str?: 'string',
14
+ int?: 'form.int',
15
+ float?: 'form.float',
16
+ decimal?: 'form.decimal',
17
+ date?: 'form.date',
18
+ date_time?: 'form.date_time',
19
+ time?: 'form.time'
20
+ }.freeze
21
+
22
+ DEFAULT_TYPE_NODE = [[:type, 'string']].freeze
23
+
24
+ def initialize
25
+ @type_compiler = Dry::Data::Compiler.new(Dry::Data)
26
+ end
27
+
28
+ def call(ast)
29
+ schema = ast.map { |node| visit(node) }
30
+ type_compiler.([:type, ['hash', [:symbolized, schema]]])
31
+ end
32
+
33
+ def visit(node, *args)
34
+ send(:"visit_#{node[0]}", node[1], *args)
35
+ end
36
+
37
+ def visit_or(node, *args)
38
+ left, right = node
39
+ [:sum, [visit(left, *args), visit(right, *args)]]
40
+ end
41
+
42
+ def visit_and(node, first = true)
43
+ if first
44
+ name, type = node.map { |n| visit(n, false) }.uniq
45
+ [:key, [name, type]]
46
+ else
47
+ result = node.map { |n| visit(n, first) }.uniq
48
+
49
+ if result.size == 1
50
+ result.first
51
+ else
52
+ (result - DEFAULT_TYPE_NODE).first
53
+ end
54
+ end
55
+ end
56
+
57
+ def visit_implication(node)
58
+ [:key, node.map(&method(:visit))]
59
+ end
60
+
61
+ def visit_key(node, *args)
62
+ node[0].to_s
63
+ end
64
+
65
+ def visit_val(node, *args)
66
+ visit(node[1], *args)
67
+ end
68
+
69
+ def visit_set(node, *args)
70
+ [:type, ['hash', [:symbolized, node[1].map { |n| visit(n) }]]]
71
+ end
72
+
73
+ def visit_predicate(node, *args)
74
+ [:type, TYPES[node[0]] || TYPES[:default]]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -17,13 +17,7 @@ module Dry
17
17
  end
18
18
 
19
19
  def self.load_yaml(path)
20
- symbolize_keys(YAML.load_file(path))
21
- end
22
-
23
- def self.symbolize_keys(hash)
24
- hash.each_with_object({}) do |(k, v), r|
25
- r[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
26
- end
20
+ Validation.symbolize_keys(YAML.load_file(path))
27
21
  end
28
22
 
29
23
  class Namespaced
@@ -11,7 +11,7 @@ module Dry
11
11
  other.import(self)
12
12
  end
13
13
 
14
- predicate(:nil?) do |input|
14
+ predicate(:none?) do |input|
15
15
  input.nil?
16
16
  end
17
17
 
@@ -32,14 +32,46 @@ module Dry
32
32
  !self[:empty?].(input)
33
33
  end
34
34
 
35
+ predicate(:bool?) do |input|
36
+ input.is_a?(TrueClass) || input.is_a?(FalseClass)
37
+ end
38
+
39
+ predicate(:date?) do |input|
40
+ input.is_a?(Date)
41
+ end
42
+
43
+ predicate(:date_time?) do |input|
44
+ input.is_a?(DateTime)
45
+ end
46
+
47
+ predicate(:time?) do |input|
48
+ input.is_a?(Time)
49
+ end
50
+
35
51
  predicate(:int?) do |input|
36
52
  input.is_a?(Fixnum)
37
53
  end
38
54
 
55
+ predicate(:float?) do |input|
56
+ input.is_a?(Float)
57
+ end
58
+
59
+ predicate(:decimal?) do |input|
60
+ input.is_a?(BigDecimal)
61
+ end
62
+
39
63
  predicate(:str?) do |input|
40
64
  input.is_a?(String)
41
65
  end
42
66
 
67
+ predicate(:hash?) do |input|
68
+ input.is_a?(Hash)
69
+ end
70
+
71
+ predicate(:array?) do |input|
72
+ input.is_a?(Array)
73
+ end
74
+
43
75
  predicate(:lt?) do |num, input|
44
76
  input < num
45
77
  end
@@ -36,6 +36,14 @@ module Dry
36
36
  @rule = rule
37
37
  end
38
38
 
39
+ def >(other)
40
+ if success?
41
+ other.(input)
42
+ else
43
+ Validation.Result(input, true, rule)
44
+ end
45
+ end
46
+
39
47
  def and(other)
40
48
  if success?
41
49
  other.(input)
@@ -5,96 +5,6 @@ module Dry
5
5
  class Rule
6
6
  include Dry::Equalizer(:name, :predicate)
7
7
 
8
- class Key < Rule
9
- def self.new(name, predicate)
10
- super(name, predicate.curry(name))
11
- end
12
-
13
- def type
14
- :key
15
- end
16
-
17
- def call(input)
18
- Validation.Result(input[name], predicate.(input), self)
19
- end
20
- end
21
-
22
- class Value < Rule
23
- def call(input)
24
- Validation.Result(input, predicate.(input), self)
25
- end
26
-
27
- def type
28
- :val
29
- end
30
- end
31
-
32
- class Composite
33
- include Dry::Equalizer(:left, :right)
34
-
35
- attr_reader :name, :left, :right
36
-
37
- def initialize(left, right)
38
- @name = left.name
39
- @left = left
40
- @right = right
41
- end
42
-
43
- def to_ary
44
- [type, left.to_ary, [right.to_ary]]
45
- end
46
- alias_method :to_a, :to_ary
47
- end
48
-
49
- class Conjunction < Composite
50
- def call(input)
51
- left.(input).and(right)
52
- end
53
-
54
- def type
55
- :and
56
- end
57
- end
58
-
59
- class Disjunction < Composite
60
- def call(input)
61
- left.(input).or(right)
62
- end
63
-
64
- def type
65
- :or
66
- end
67
- end
68
-
69
- class Each < Rule
70
- def call(input)
71
- Validation.Result(input, input.map { |element| predicate.(element) }, self)
72
- end
73
-
74
- def type
75
- :each
76
- end
77
- end
78
-
79
- class Set < Rule
80
- def call(input)
81
- Validation.Result(input, predicate.map { |rule| rule.(input) }, self)
82
- end
83
-
84
- def type
85
- :set
86
- end
87
-
88
- def at(*args)
89
- self.class.new(name, predicate.values_at(*args))
90
- end
91
-
92
- def to_ary
93
- [type, [name, predicate.map(&:to_ary)]]
94
- end
95
- alias_method :to_a, :to_ary
96
- end
97
-
98
8
  attr_reader :name, :predicate
99
9
 
100
10
  def initialize(name, predicate)
@@ -117,9 +27,20 @@ module Dry
117
27
  end
118
28
  alias_method :|, :or
119
29
 
30
+ def then(other)
31
+ Implication.new(self, other)
32
+ end
33
+ alias_method :>, :then
34
+
120
35
  def curry(*args)
121
36
  self.class.new(name, predicate.curry(*args))
122
37
  end
123
38
  end
124
39
  end
125
40
  end
41
+
42
+ require 'dry/validation/rule/key'
43
+ require 'dry/validation/rule/value'
44
+ require 'dry/validation/rule/each'
45
+ require 'dry/validation/rule/set'
46
+ require 'dry/validation/rule/composite'
@@ -0,0 +1,50 @@
1
+ module Dry
2
+ module Validation
3
+ class Rule::Composite < Rule
4
+ include Dry::Equalizer(:left, :right)
5
+
6
+ attr_reader :name, :left, :right
7
+
8
+ def initialize(left, right)
9
+ @name = left.name
10
+ @left = left
11
+ @right = right
12
+ end
13
+
14
+ def to_ary
15
+ [type, [left.to_ary, right.to_ary]]
16
+ end
17
+ alias_method :to_a, :to_ary
18
+ end
19
+
20
+ class Rule::Implication < Rule::Composite
21
+ def call(input)
22
+ left.(input) > right
23
+ end
24
+
25
+ def type
26
+ :implication
27
+ end
28
+ end
29
+
30
+ class Rule::Conjunction < Rule::Composite
31
+ def call(input)
32
+ left.(input).and(right)
33
+ end
34
+
35
+ def type
36
+ :and
37
+ end
38
+ end
39
+
40
+ class Rule::Disjunction < Rule::Composite
41
+ def call(input)
42
+ left.(input).or(right)
43
+ end
44
+
45
+ def type
46
+ :or
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ module Dry
2
+ module Validation
3
+ class Rule::Each < Rule
4
+ def call(input)
5
+ Validation.Result(input, input.map { |element| predicate.(element) }, self)
6
+ end
7
+
8
+ def type
9
+ :each
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module Dry
2
+ module Validation
3
+ class Rule::Key < Rule
4
+ def self.new(name, predicate)
5
+ super(name, predicate.curry(name))
6
+ end
7
+
8
+ def type
9
+ :key
10
+ end
11
+
12
+ def call(input)
13
+ Validation.Result(input[name], predicate.(input), self)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ module Dry
2
+ module Validation
3
+ class Rule::Set < Rule
4
+ def call(input)
5
+ Validation.Result(input, predicate.map { |rule| rule.(input) }, self)
6
+ end
7
+
8
+ def type
9
+ :set
10
+ end
11
+
12
+ def at(*args)
13
+ self.class.new(name, predicate.values_at(*args))
14
+ end
15
+
16
+ def to_ary
17
+ [type, [name, predicate.map(&:to_ary)]]
18
+ end
19
+ alias_method :to_a, :to_ary
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Dry
2
+ module Validation
3
+ class Rule::Value < Rule
4
+ def call(input)
5
+ Validation.Result(input, predicate.(input), self)
6
+ end
7
+
8
+ def type
9
+ :val
10
+ end
11
+ end
12
+ end
13
+ end
@@ -52,6 +52,11 @@ module Dry
52
52
  left, right = node
53
53
  visit(left) | visit(right)
54
54
  end
55
+
56
+ def visit_implication(node)
57
+ left, right = node
58
+ visit(left) > visit(right)
59
+ end
55
60
  end
56
61
  end
57
62
  end
@@ -4,6 +4,7 @@ require 'dry/validation/error'
4
4
  require 'dry/validation/rule_compiler'
5
5
  require 'dry/validation/messages'
6
6
  require 'dry/validation/error_compiler'
7
+ require 'dry/validation/schema/result'
7
8
 
8
9
  module Dry
9
10
  module Validation
@@ -52,14 +53,17 @@ module Dry
52
53
  end
53
54
 
54
55
  def call(input)
55
- rules.each_with_object(Error::Set.new) do |rule, errors|
56
+ error_set = rules.each_with_object(Error::Set.new) do |rule, errors|
56
57
  result = rule.(input)
57
58
  errors << Error.new(result) if result.failure?
58
59
  end
60
+
61
+ Result.new(input, error_set)
59
62
  end
60
63
 
61
64
  def messages(input)
62
- error_compiler.call(call(input).map(&:to_ary))
65
+ result = call(input)
66
+ Result.new(result.params, error_compiler.(result.to_ary))
63
67
  end
64
68
 
65
69
  def [](name)