dry-validation 0.1.0 → 0.2.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 (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)