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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.rubocop_todo.yml +7 -0
- data/.travis.yml +29 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.md +297 -0
- data/Rakefile +12 -0
- data/config/errors.yml +35 -0
- data/dry-validation.gemspec +25 -0
- data/examples/basic.rb +21 -0
- data/examples/nested.rb +30 -0
- data/examples/rule_ast.rb +33 -0
- data/lib/dry-validation.rb +1 -0
- data/lib/dry/validation.rb +12 -0
- data/lib/dry/validation/error.rb +43 -0
- data/lib/dry/validation/error_compiler.rb +116 -0
- data/lib/dry/validation/messages.rb +71 -0
- data/lib/dry/validation/predicate.rb +39 -0
- data/lib/dry/validation/predicate_set.rb +22 -0
- data/lib/dry/validation/predicates.rb +88 -0
- data/lib/dry/validation/result.rb +64 -0
- data/lib/dry/validation/rule.rb +125 -0
- data/lib/dry/validation/rule_compiler.rb +57 -0
- data/lib/dry/validation/schema.rb +74 -0
- data/lib/dry/validation/schema/definition.rb +15 -0
- data/lib/dry/validation/schema/key.rb +39 -0
- data/lib/dry/validation/schema/rule.rb +28 -0
- data/lib/dry/validation/schema/value.rb +31 -0
- data/lib/dry/validation/version.rb +5 -0
- data/rakelib/rubocop.rake +18 -0
- data/spec/fixtures/errors.yml +4 -0
- data/spec/integration/custom_error_messages_spec.rb +35 -0
- data/spec/integration/custom_predicates_spec.rb +57 -0
- data/spec/integration/validation_spec.rb +118 -0
- data/spec/shared/predicates.rb +31 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/unit/error_compiler_spec.rb +165 -0
- data/spec/unit/predicate_spec.rb +37 -0
- data/spec/unit/predicates/empty_spec.rb +38 -0
- data/spec/unit/predicates/eql_spec.rb +21 -0
- data/spec/unit/predicates/exclusion_spec.rb +35 -0
- data/spec/unit/predicates/filled_spec.rb +38 -0
- data/spec/unit/predicates/format_spec.rb +21 -0
- data/spec/unit/predicates/gt_spec.rb +40 -0
- data/spec/unit/predicates/gteq_spec.rb +40 -0
- data/spec/unit/predicates/inclusion_spec.rb +35 -0
- data/spec/unit/predicates/int_spec.rb +34 -0
- data/spec/unit/predicates/key_spec.rb +29 -0
- data/spec/unit/predicates/lt_spec.rb +40 -0
- data/spec/unit/predicates/lteq_spec.rb +40 -0
- data/spec/unit/predicates/max_size_spec.rb +49 -0
- data/spec/unit/predicates/min_size_spec.rb +49 -0
- data/spec/unit/predicates/nil_spec.rb +28 -0
- data/spec/unit/predicates/size_spec.rb +49 -0
- data/spec/unit/predicates/str_spec.rb +32 -0
- data/spec/unit/rule/each_spec.rb +20 -0
- data/spec/unit/rule/key_spec.rb +27 -0
- data/spec/unit/rule/set_spec.rb +32 -0
- data/spec/unit/rule/value_spec.rb +42 -0
- data/spec/unit/rule_compiler_spec.rb +86 -0
- 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
|