dry-validation 0.1.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 (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