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.
- 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)
|