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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +23 -0
- data/README.md +203 -26
- data/config/errors.yml +16 -0
- data/dry-validation.gemspec +1 -0
- data/examples/each.rb +19 -0
- data/examples/form.rb +15 -0
- data/examples/nested.rb +13 -11
- data/lib/dry/validation.rb +5 -0
- data/lib/dry/validation/error_compiler.rb +2 -18
- data/lib/dry/validation/input_type_compiler.rb +78 -0
- data/lib/dry/validation/messages.rb +1 -7
- data/lib/dry/validation/predicates.rb +33 -1
- data/lib/dry/validation/result.rb +8 -0
- data/lib/dry/validation/rule.rb +11 -90
- data/lib/dry/validation/rule/composite.rb +50 -0
- data/lib/dry/validation/rule/each.rb +13 -0
- data/lib/dry/validation/rule/key.rb +17 -0
- data/lib/dry/validation/rule/set.rb +22 -0
- data/lib/dry/validation/rule/value.rb +13 -0
- data/lib/dry/validation/rule_compiler.rb +5 -0
- data/lib/dry/validation/schema.rb +6 -2
- data/lib/dry/validation/schema/definition.rb +4 -0
- data/lib/dry/validation/schema/form.rb +19 -0
- data/lib/dry/validation/schema/key.rb +16 -3
- data/lib/dry/validation/schema/result.rb +29 -0
- data/lib/dry/validation/schema/rule.rb +14 -16
- data/lib/dry/validation/schema/value.rb +14 -2
- data/lib/dry/validation/version.rb +1 -1
- data/spec/integration/custom_error_messages_spec.rb +1 -1
- data/spec/integration/optional_keys_spec.rb +30 -0
- data/spec/integration/schema_form_spec.rb +99 -0
- data/spec/integration/{validation_spec.rb → schema_spec.rb} +40 -13
- data/spec/shared/predicates.rb +1 -1
- data/spec/unit/error_compiler_spec.rb +64 -0
- data/spec/unit/input_type_compiler_spec.rb +205 -0
- data/spec/unit/predicates/bool_spec.rb +34 -0
- data/spec/unit/predicates/date_spec.rb +31 -0
- data/spec/unit/predicates/date_time_spec.rb +31 -0
- data/spec/unit/predicates/decimal_spec.rb +32 -0
- data/spec/unit/predicates/float_spec.rb +31 -0
- data/spec/unit/predicates/{nil_spec.rb → none_spec.rb} +2 -2
- data/spec/unit/predicates/time_spec.rb +31 -0
- data/spec/unit/rule/conjunction_spec.rb +28 -0
- data/spec/unit/rule/disjunction_spec.rb +36 -0
- data/spec/unit/rule/implication_spec.rb +14 -0
- data/spec/unit/rule/value_spec.rb +1 -1
- 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(:
|
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
|
data/lib/dry/validation/rule.rb
CHANGED
@@ -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,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
|
@@ -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
|
-
|
65
|
+
result = call(input)
|
66
|
+
Result.new(result.params, error_compiler.(result.to_ary))
|
63
67
|
end
|
64
68
|
|
65
69
|
def [](name)
|