dry-validation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +16 -0
  5. data/.rubocop_todo.yml +7 -0
  6. data/.travis.yml +29 -0
  7. data/CHANGELOG.md +3 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE +20 -0
  10. data/README.md +297 -0
  11. data/Rakefile +12 -0
  12. data/config/errors.yml +35 -0
  13. data/dry-validation.gemspec +25 -0
  14. data/examples/basic.rb +21 -0
  15. data/examples/nested.rb +30 -0
  16. data/examples/rule_ast.rb +33 -0
  17. data/lib/dry-validation.rb +1 -0
  18. data/lib/dry/validation.rb +12 -0
  19. data/lib/dry/validation/error.rb +43 -0
  20. data/lib/dry/validation/error_compiler.rb +116 -0
  21. data/lib/dry/validation/messages.rb +71 -0
  22. data/lib/dry/validation/predicate.rb +39 -0
  23. data/lib/dry/validation/predicate_set.rb +22 -0
  24. data/lib/dry/validation/predicates.rb +88 -0
  25. data/lib/dry/validation/result.rb +64 -0
  26. data/lib/dry/validation/rule.rb +125 -0
  27. data/lib/dry/validation/rule_compiler.rb +57 -0
  28. data/lib/dry/validation/schema.rb +74 -0
  29. data/lib/dry/validation/schema/definition.rb +15 -0
  30. data/lib/dry/validation/schema/key.rb +39 -0
  31. data/lib/dry/validation/schema/rule.rb +28 -0
  32. data/lib/dry/validation/schema/value.rb +31 -0
  33. data/lib/dry/validation/version.rb +5 -0
  34. data/rakelib/rubocop.rake +18 -0
  35. data/spec/fixtures/errors.yml +4 -0
  36. data/spec/integration/custom_error_messages_spec.rb +35 -0
  37. data/spec/integration/custom_predicates_spec.rb +57 -0
  38. data/spec/integration/validation_spec.rb +118 -0
  39. data/spec/shared/predicates.rb +31 -0
  40. data/spec/spec_helper.rb +18 -0
  41. data/spec/unit/error_compiler_spec.rb +165 -0
  42. data/spec/unit/predicate_spec.rb +37 -0
  43. data/spec/unit/predicates/empty_spec.rb +38 -0
  44. data/spec/unit/predicates/eql_spec.rb +21 -0
  45. data/spec/unit/predicates/exclusion_spec.rb +35 -0
  46. data/spec/unit/predicates/filled_spec.rb +38 -0
  47. data/spec/unit/predicates/format_spec.rb +21 -0
  48. data/spec/unit/predicates/gt_spec.rb +40 -0
  49. data/spec/unit/predicates/gteq_spec.rb +40 -0
  50. data/spec/unit/predicates/inclusion_spec.rb +35 -0
  51. data/spec/unit/predicates/int_spec.rb +34 -0
  52. data/spec/unit/predicates/key_spec.rb +29 -0
  53. data/spec/unit/predicates/lt_spec.rb +40 -0
  54. data/spec/unit/predicates/lteq_spec.rb +40 -0
  55. data/spec/unit/predicates/max_size_spec.rb +49 -0
  56. data/spec/unit/predicates/min_size_spec.rb +49 -0
  57. data/spec/unit/predicates/nil_spec.rb +28 -0
  58. data/spec/unit/predicates/size_spec.rb +49 -0
  59. data/spec/unit/predicates/str_spec.rb +32 -0
  60. data/spec/unit/rule/each_spec.rb +20 -0
  61. data/spec/unit/rule/key_spec.rb +27 -0
  62. data/spec/unit/rule/set_spec.rb +32 -0
  63. data/spec/unit/rule/value_spec.rb +42 -0
  64. data/spec/unit/rule_compiler_spec.rb +86 -0
  65. metadata +230 -0
@@ -0,0 +1,64 @@
1
+ module Dry
2
+ module Validation
3
+ def self.Result(input, value, rule)
4
+ case value
5
+ when Array then Result::Set.new(input, value, rule)
6
+ else Result::Value.new(input, value, rule)
7
+ end
8
+ end
9
+
10
+ class Result
11
+ include Dry::Equalizer(:success?, :input, :rule)
12
+
13
+ attr_reader :input, :value, :rule
14
+
15
+ class Set < Result
16
+ def success?
17
+ value.all?(&:success?)
18
+ end
19
+
20
+ def to_ary
21
+ indices = value.map { |v| v.failure? ? value.index(v) : nil }.compact
22
+ [:input, [rule.name, input, value.values_at(*indices).map(&:to_ary)]]
23
+ end
24
+ end
25
+
26
+ class Value < Result
27
+ def to_ary
28
+ [:input, [rule.name, input, [rule.to_ary]]]
29
+ end
30
+ alias_method :to_a, :to_ary
31
+ end
32
+
33
+ def initialize(input, value, rule)
34
+ @input = input
35
+ @value = value
36
+ @rule = rule
37
+ end
38
+
39
+ def and(other)
40
+ if success?
41
+ other.(input)
42
+ else
43
+ self
44
+ end
45
+ end
46
+
47
+ def or(other)
48
+ if success?
49
+ self
50
+ else
51
+ other.(input)
52
+ end
53
+ end
54
+
55
+ def success?
56
+ @value
57
+ end
58
+
59
+ def failure?
60
+ ! success?
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,125 @@
1
+ require 'dry/validation/result'
2
+
3
+ module Dry
4
+ module Validation
5
+ class Rule
6
+ include Dry::Equalizer(:name, :predicate)
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
+ attr_reader :name, :predicate
99
+
100
+ def initialize(name, predicate)
101
+ @name = name
102
+ @predicate = predicate
103
+ end
104
+
105
+ def to_ary
106
+ [type, [name, predicate.to_ary]]
107
+ end
108
+ alias_method :to_a, :to_ary
109
+
110
+ def and(other)
111
+ Conjunction.new(self, other)
112
+ end
113
+ alias_method :&, :and
114
+
115
+ def or(other)
116
+ Disjunction.new(self, other)
117
+ end
118
+ alias_method :|, :or
119
+
120
+ def curry(*args)
121
+ self.class.new(name, predicate.curry(*args))
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,57 @@
1
+ require 'dry/validation/rule'
2
+
3
+ module Dry
4
+ module Validation
5
+ class RuleCompiler
6
+ attr_reader :predicates
7
+
8
+ def initialize(predicates)
9
+ @predicates = predicates
10
+ end
11
+
12
+ def call(ast)
13
+ ast.map { |node| visit(node) }
14
+ end
15
+
16
+ def visit(node)
17
+ name, nodes = node
18
+ send(:"visit_#{name}", nodes)
19
+ end
20
+
21
+ def visit_key(node)
22
+ name, predicate = node
23
+ Rule::Key.new(name, visit(predicate))
24
+ end
25
+
26
+ def visit_val(node)
27
+ name, predicate = node
28
+ Rule::Value.new(name, visit(predicate))
29
+ end
30
+
31
+ def visit_set(node)
32
+ name, rules = node
33
+ Rule::Set.new(name, call(rules))
34
+ end
35
+
36
+ def visit_each(node)
37
+ name, rule = node
38
+ Rule::Each.new(name, visit(rule))
39
+ end
40
+
41
+ def visit_predicate(node)
42
+ name, args = node
43
+ predicates[name].curry(*args)
44
+ end
45
+
46
+ def visit_and(node)
47
+ left, right = node
48
+ visit(left) & visit(right)
49
+ end
50
+
51
+ def visit_or(node)
52
+ left, right = node
53
+ visit(left) | visit(right)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,74 @@
1
+ require 'dry/validation/schema/definition'
2
+ require 'dry/validation/predicates'
3
+ require 'dry/validation/error'
4
+ require 'dry/validation/rule_compiler'
5
+ require 'dry/validation/messages'
6
+ require 'dry/validation/error_compiler'
7
+
8
+ module Dry
9
+ module Validation
10
+ class Schema
11
+ extend Dry::Configurable
12
+ extend Definition
13
+
14
+ setting :predicates, Predicates
15
+ setting :messages, Messages.default
16
+ setting :messages_file
17
+ setting :namespace
18
+
19
+ def self.predicates
20
+ config.predicates
21
+ end
22
+
23
+ def self.error_compiler
24
+ ErrorCompiler.new(messages)
25
+ end
26
+
27
+ def self.messages
28
+ default = config.messages
29
+
30
+ if config.messages_file && config.namespace
31
+ default.merge(config.messages_file).namespaced(config.namespace)
32
+ elsif config.messages_file
33
+ default.merge(config.messages_file)
34
+ elsif config.namespace
35
+ default.namespaced(config.namespace)
36
+ else
37
+ default
38
+ end
39
+ end
40
+
41
+ def self.rules
42
+ @__rules__ ||= []
43
+ end
44
+
45
+ attr_reader :rules
46
+
47
+ attr_reader :error_compiler
48
+
49
+ def initialize(error_compiler = self.class.error_compiler)
50
+ @rules = RuleCompiler.new(self).(self.class.rules.map(&:to_ary))
51
+ @error_compiler = error_compiler
52
+ end
53
+
54
+ def call(input)
55
+ rules.each_with_object(Error::Set.new) do |rule, errors|
56
+ result = rule.(input)
57
+ errors << Error.new(result) if result.failure?
58
+ end
59
+ end
60
+
61
+ def messages(input)
62
+ error_compiler.call(call(input).map(&:to_ary))
63
+ end
64
+
65
+ def [](name)
66
+ if methods.include?(name)
67
+ Predicate.new(name, &method(name))
68
+ else
69
+ self.class.predicates[name]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,15 @@
1
+ module Dry
2
+ module Validation
3
+ class Schema
4
+ module Definition
5
+ def key(name, &block)
6
+ Key.new(name, rules).key?(&block)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ require 'dry/validation/schema/rule'
14
+ require 'dry/validation/schema/value'
15
+ require 'dry/validation/schema/key'
@@ -0,0 +1,39 @@
1
+ require 'dry/validation/rule'
2
+
3
+ module Dry
4
+ module Validation
5
+ class Schema
6
+ class Key
7
+ attr_reader :name, :rules
8
+
9
+ def initialize(name, rules, &block)
10
+ @name = name
11
+ @rules = rules
12
+ end
13
+
14
+ private
15
+
16
+ def method_missing(meth, *args, &block)
17
+ key_rule = [:key, [name, [:predicate, [meth, args]]]]
18
+
19
+ if block
20
+ val_rule = yield(Value.new(name))
21
+
22
+ rules <<
23
+ if val_rule.is_a?(Array)
24
+ Definition::Rule.new([:and, [key_rule, [:set, [name, val_rule.map(&:to_ary)]]]])
25
+ else
26
+ Definition::Rule.new([:and, [key_rule, val_rule.to_ary]])
27
+ end
28
+ else
29
+ Definition::Rule.new(key_rule)
30
+ end
31
+ end
32
+
33
+ def respond_to_missing?(meth, _include_private = false)
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ module Dry
2
+ module Validation
3
+ class Schema
4
+ module Definition
5
+ class Rule
6
+ attr_reader :node
7
+
8
+ def initialize(node)
9
+ @node = node
10
+ end
11
+
12
+ def to_ary
13
+ node
14
+ end
15
+ alias_method :to_a, :to_ary
16
+
17
+ def &(other)
18
+ self.class.new([:and, [node, other.to_ary]])
19
+ end
20
+
21
+ def |(other)
22
+ self.class.new([:or, [node, other.to_ary]])
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ module Dry
2
+ module Validation
3
+ class Schema
4
+ class Value
5
+ include Schema::Definition
6
+
7
+ attr_reader :name, :rules
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ @rules = []
12
+ end
13
+
14
+ def each(&block)
15
+ rule = yield(self).to_ary
16
+ Definition::Rule.new([:each, [name, rule]])
17
+ end
18
+
19
+ private
20
+
21
+ def method_missing(meth, *args, &block)
22
+ Definition::Rule.new([:val, [name, [:predicate, [meth, args]]]])
23
+ end
24
+
25
+ def respond_to_missing?(meth, _include_private = false)
26
+ true
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ module Dry
2
+ module Validation
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end