dry-validation 0.5.0 → 0.6.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/.codeclimate.yml +16 -0
- data/CHANGELOG.md +23 -2
- data/Gemfile +1 -0
- data/config/errors.yml +2 -0
- data/dry-validation.gemspec +3 -3
- data/lib/dry/validation/error_compiler.rb +46 -10
- data/lib/dry/validation/hint_compiler.rb +5 -0
- data/lib/dry/validation/result.rb +5 -1
- data/lib/dry/validation/schema.rb +14 -11
- data/lib/dry/validation/schema/attr.rb +37 -0
- data/lib/dry/validation/schema/definition.rb +23 -5
- data/lib/dry/validation/schema/form.rb +16 -0
- data/lib/dry/validation/schema/key.rb +1 -1
- data/lib/dry/validation/schema/result.rb +11 -3
- data/lib/dry/validation/schema/rule.rb +23 -1
- data/lib/dry/validation/version.rb +1 -1
- data/spec/integration/error_compiler_spec.rb +3 -0
- data/spec/integration/injecting_rules_spec.rb +23 -0
- data/spec/integration/rule_groups_spec.rb +59 -0
- data/spec/integration/schema/attrs_spec.rb +38 -0
- data/spec/integration/schema/check_rules_spec.rb +63 -17
- data/spec/integration/schema/default_key_behavior_spec.rb +23 -0
- data/spec/integration/schema/grouped_rules_spec.rb +57 -0
- data/spec/integration/schema_spec.rb +175 -10
- data/spec/spec_helper.rb +5 -0
- data/spec/support/define_struct.rb +25 -0
- data/spec/unit/hint_compiler_spec.rb +18 -1
- data/spec/unit/input_type_compiler_spec.rb +2 -2
- data/spec/unit/schema/rule_spec.rb +4 -4
- data/spec/unit/schema/value_spec.rb +2 -2
- metadata +29 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 341f5b1e89f76e09d7a1d8d2d20cdb68a50a88c1
|
4
|
+
data.tar.gz: 6dada3ab87730f7fe97e600bb73868c2afba511f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 331d2cd4ea7c0f2b3b96046ad46faf7de8746b1538aaaba8f0fdf07a71546088962653d96cc8b71a3ecba0b04850367b19841f0418d083702666e154890bf29c
|
7
|
+
data.tar.gz: ec3ec948092ab30ad982498ca234b385c66e136ec351cc0b8754f041a46a23cc59049b34fae76aec31d2cc46a2f11d91b9f469c862cc991c4605531600d31d39
|
data/.codeclimate.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
engines:
|
2
|
+
rubocop:
|
3
|
+
enabled: true
|
4
|
+
checks:
|
5
|
+
Rubocop/Style/LambdaCall:
|
6
|
+
enabled: false
|
7
|
+
Rubocop/Style/StabbyLambdaParentheses:
|
8
|
+
enabled: false
|
9
|
+
Rubocop/Metrics/CyclomaticComplexity:
|
10
|
+
Max: 10
|
11
|
+
ratings:
|
12
|
+
paths:
|
13
|
+
- lib/**/*.rb
|
14
|
+
exclude_paths:
|
15
|
+
- spec/**/*
|
16
|
+
- examples/**/*
|
data/CHANGELOG.md
CHANGED
@@ -1,15 +1,36 @@
|
|
1
|
+
# v0.6.0 2016-01-20
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Support for validating objects with attr readers via `attr` (SunnyMagadan)
|
6
|
+
* Support for `value` interface in the DSL for composing high-level rules based on values (solnic)
|
7
|
+
* Support for `rule(name: :something)` syntax for grouping high-level rules under
|
8
|
+
the same name (solnic)
|
9
|
+
* Support for `confirmation(:foo, some_predicate: some_argument)` shortcut syntax (solnic)
|
10
|
+
* Support for error messages for grouped rules (like `confirmation`) (solnic)
|
11
|
+
* Schemas support injecting rules from the outside (solnic)
|
12
|
+
|
13
|
+
## Changed
|
14
|
+
|
15
|
+
* `rule` uses objects that inherit from `BasicObject` to avoid conflicts with
|
16
|
+
predicate names and built-in `Object` methods (solnic)
|
17
|
+
* In `Schema::Form` both `key` and `optional` will apply `filled?` predicate by
|
18
|
+
default when no block is passed (solnic)
|
19
|
+
|
20
|
+
[Compare v0.5.0...v0.6.0](https://github.com/dryrb/dry-validation/compare/v0.5.0...v0.6.0)
|
21
|
+
|
1
22
|
# v0.5.0 2016-01-11
|
2
23
|
|
3
24
|
### Changed
|
4
25
|
|
5
|
-
* Now depends on
|
26
|
+
* Now depends on [dry-logic](https://github.com/dryrb/dry-logic) for predicates and rules (solnic)
|
6
27
|
* `dry/validation/schema/form` is now required by default (solnic)
|
7
28
|
|
8
29
|
### Fixed
|
9
30
|
|
10
31
|
* `Schema::Form` uses safe `form.array` and `form.hash` types which fixes #42 (solnic)
|
11
32
|
|
12
|
-
[Compare v0.
|
33
|
+
[Compare v0.4.1...v0.5.0](https://github.com/dryrb/dry-validation/compare/v0.4.1...v0.5.0)
|
13
34
|
|
14
35
|
# v0.4.1 2015-12-27
|
15
36
|
|
data/Gemfile
CHANGED
data/config/errors.yml
CHANGED
data/dry-validation.gemspec
CHANGED
@@ -15,10 +15,10 @@ Gem::Specification.new do |spec|
|
|
15
15
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
16
|
spec.require_paths = ['lib']
|
17
17
|
|
18
|
-
spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
|
19
|
-
spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.
|
18
|
+
spec.add_runtime_dependency 'dry-configurable', '~> 0.1', '>= 0.1.3'
|
19
|
+
spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.8'
|
20
20
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
21
|
-
spec.add_runtime_dependency 'dry-logic', '~> 0.1'
|
21
|
+
spec.add_runtime_dependency 'dry-logic', '~> 0.1', '>= 0.1.2'
|
22
22
|
spec.add_runtime_dependency 'dry-data', '~> 0.5', '>= 0.5.0'
|
23
23
|
|
24
24
|
spec.add_development_dependency 'bundler'
|
@@ -4,6 +4,7 @@ module Dry
|
|
4
4
|
attr_reader :messages, :options
|
5
5
|
|
6
6
|
DEFAULT_RESULT = {}.freeze
|
7
|
+
KEY_SEPARATOR = '.'.freeze
|
7
8
|
|
8
9
|
def initialize(messages, options = {})
|
9
10
|
@messages = messages
|
@@ -11,7 +12,7 @@ module Dry
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def call(ast)
|
14
|
-
ast.map { |node| visit(node) }
|
15
|
+
merge(ast.map { |node| visit(node) }) || DEFAULT_RESULT
|
15
16
|
end
|
16
17
|
|
17
18
|
def with(new_options)
|
@@ -26,13 +27,25 @@ module Dry
|
|
26
27
|
visit(error)
|
27
28
|
end
|
28
29
|
|
29
|
-
def visit_input(input, *
|
30
|
-
name
|
31
|
-
|
30
|
+
def visit_input(input, *)
|
31
|
+
name = normalize_name(input[0])
|
32
|
+
_, value, rules = input
|
33
|
+
errors = [rules.map { |rule| visit(rule, name, value) }, value]
|
34
|
+
|
35
|
+
if input[0].is_a?(Hash)
|
36
|
+
root, sub = input[0].to_a.flatten
|
37
|
+
{ root => { sub => errors } }
|
38
|
+
else
|
39
|
+
{ input[0] => errors }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_group(_, name, _)
|
44
|
+
messages[name, rule: name]
|
32
45
|
end
|
33
46
|
|
34
|
-
def visit_check(node, *
|
35
|
-
name
|
47
|
+
def visit_check(node, *)
|
48
|
+
name = normalize_name(node[0])
|
36
49
|
messages[name, rule: name]
|
37
50
|
end
|
38
51
|
|
@@ -41,6 +54,11 @@ module Dry
|
|
41
54
|
visit(predicate, value, name)
|
42
55
|
end
|
43
56
|
|
57
|
+
def visit_attr(rule, name, value)
|
58
|
+
_, predicate = rule
|
59
|
+
visit(predicate, value, name)
|
60
|
+
end
|
61
|
+
|
44
62
|
def visit_val(rule, name, value)
|
45
63
|
name, predicate = rule
|
46
64
|
visit(predicate, value, name)
|
@@ -59,15 +77,19 @@ module Dry
|
|
59
77
|
template % tokens
|
60
78
|
end
|
61
79
|
|
62
|
-
def visit_key?(*args,
|
80
|
+
def visit_key?(*args, _value)
|
63
81
|
{ name: args[0][0] }
|
64
82
|
end
|
65
83
|
|
66
|
-
def
|
84
|
+
def visit_attr?(*args, _value)
|
85
|
+
{ name: args[0][0] }
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_exclusion?(*args, _value)
|
67
89
|
{ list: args[0][0].join(', ') }
|
68
90
|
end
|
69
91
|
|
70
|
-
def visit_inclusion?(*args,
|
92
|
+
def visit_inclusion?(*args, _value)
|
71
93
|
{ list: args[0][0].join(', ') }
|
72
94
|
end
|
73
95
|
|
@@ -113,7 +135,21 @@ module Dry
|
|
113
135
|
end
|
114
136
|
end
|
115
137
|
|
116
|
-
|
138
|
+
private
|
139
|
+
|
140
|
+
def normalize_name(name)
|
141
|
+
Array(name).join(KEY_SEPARATOR).to_sym
|
142
|
+
end
|
143
|
+
|
144
|
+
def merge(result)
|
145
|
+
result.reduce do |a, e|
|
146
|
+
e.merge(a) do |_, l, r|
|
147
|
+
l.is_a?(Hash) ? l.merge(r) : l + r
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def method_missing(_meth, *args)
|
117
153
|
{ value: args[1] }
|
118
154
|
end
|
119
155
|
end
|
@@ -13,8 +13,12 @@ module Dry
|
|
13
13
|
rule_results.each(&block)
|
14
14
|
end
|
15
15
|
|
16
|
+
def [](name)
|
17
|
+
to_h[name]
|
18
|
+
end
|
19
|
+
|
16
20
|
def to_h
|
17
|
-
each_with_object({}) { |result, hash| hash[result.name] = result }
|
21
|
+
@to_h ||= each_with_object({}) { |result, hash| hash[result.name] = result }
|
18
22
|
end
|
19
23
|
|
20
24
|
def merge!(other)
|
@@ -38,7 +38,7 @@ module Dry
|
|
38
38
|
when :yaml then Messages.default
|
39
39
|
when :i18n then Messages::I18n.new
|
40
40
|
else
|
41
|
-
|
41
|
+
fail "+#{config.messages}+ is not a valid messages identifier"
|
42
42
|
end
|
43
43
|
|
44
44
|
if config.messages_file && config.namespace
|
@@ -70,18 +70,20 @@ module Dry
|
|
70
70
|
|
71
71
|
attr_reader :rules, :schemas, :groups, :checks
|
72
72
|
|
73
|
+
attr_reader :rule_compiler
|
74
|
+
|
73
75
|
attr_reader :error_compiler
|
74
76
|
|
75
77
|
attr_reader :hint_compiler
|
76
78
|
|
77
|
-
def initialize(
|
78
|
-
|
79
|
-
@rules =
|
80
|
-
@checks = self.class.checks
|
81
|
-
@groups =
|
79
|
+
def initialize(rules = [])
|
80
|
+
@rule_compiler = Logic::RuleCompiler.new(self)
|
81
|
+
@rules = rule_compiler.(self.class.rules.map(&:to_ary) + rules.map(&:to_ary))
|
82
|
+
@checks = self.class.checks.map(&:to_ary)
|
83
|
+
@groups = rule_compiler.(self.class.groups.map(&:to_ary))
|
82
84
|
@schemas = self.class.schemas.map(&:new)
|
83
|
-
@error_compiler = error_compiler
|
84
|
-
@hint_compiler = hint_compiler
|
85
|
+
@error_compiler = self.class.error_compiler
|
86
|
+
@hint_compiler = self.class.hint_compiler
|
85
87
|
end
|
86
88
|
|
87
89
|
def call(input)
|
@@ -92,10 +94,11 @@ module Dry
|
|
92
94
|
end
|
93
95
|
|
94
96
|
if checks.size > 0
|
95
|
-
|
97
|
+
resolver = -> name { result[name] || self[name] }
|
98
|
+
compiled_checks = Logic::RuleCompiler.new(resolver).(checks)
|
96
99
|
|
97
100
|
compiled_checks.each do |rule|
|
98
|
-
result << rule.()
|
101
|
+
result << rule.(result)
|
99
102
|
end
|
100
103
|
end
|
101
104
|
|
@@ -116,7 +119,7 @@ module Dry
|
|
116
119
|
elsif respond_to?(name)
|
117
120
|
Logic::Predicate.new(name, &method(name))
|
118
121
|
else
|
119
|
-
|
122
|
+
fail ArgumentError, "+#{name}+ is not a valid predicate name"
|
120
123
|
end
|
121
124
|
end
|
122
125
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Dry
|
2
|
+
module Validation
|
3
|
+
class Schema
|
4
|
+
class Attr < BasicObject
|
5
|
+
attr_reader :name, :rules
|
6
|
+
|
7
|
+
def initialize(name, rules, &block)
|
8
|
+
@name = name
|
9
|
+
@rules = rules
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def method_missing(meth, *args, &block)
|
15
|
+
attr_rule = [:attr, [name, [:predicate, [meth, args]]]]
|
16
|
+
|
17
|
+
if block
|
18
|
+
val_rule = yield(Value.new(name))
|
19
|
+
|
20
|
+
rules <<
|
21
|
+
if val_rule.is_a?(::Array)
|
22
|
+
Schema::Rule.new(name, [:and, [attr_rule, [:set, [name, val_rule.map(&:to_ary)]]]])
|
23
|
+
else
|
24
|
+
Schema::Rule.new(name, [:and, [attr_rule, val_rule.to_ary]])
|
25
|
+
end
|
26
|
+
else
|
27
|
+
Schema::Rule.new(name, attr_rule)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def respond_to_missing?(*)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -13,10 +13,18 @@ module Dry
|
|
13
13
|
Key.new(name, rules).key?(&block)
|
14
14
|
end
|
15
15
|
|
16
|
+
def attr(name, &block)
|
17
|
+
Attr.new(name, rules).attr?(&block)
|
18
|
+
end
|
19
|
+
|
16
20
|
def optional(name, &block)
|
17
21
|
Key.new(name, rules).optional(&block)
|
18
22
|
end
|
19
23
|
|
24
|
+
def value(name)
|
25
|
+
Schema::Rule::Result.new(name, [])
|
26
|
+
end
|
27
|
+
|
20
28
|
def rule(name, **options, &block)
|
21
29
|
if options.any?
|
22
30
|
predicate, rule_names = options.to_a.first
|
@@ -32,13 +40,22 @@ module Dry
|
|
32
40
|
end
|
33
41
|
end
|
34
42
|
|
35
|
-
def confirmation(name)
|
36
|
-
|
43
|
+
def confirmation(name, options = {})
|
44
|
+
conf_name = :"#{name}_confirmation"
|
45
|
+
|
46
|
+
unless rule_by_name(name)
|
47
|
+
if options.any?
|
48
|
+
key(name) do |value|
|
49
|
+
options.map { |p, args| value.__send__(:"#{p}?", *args) }.reduce(:&)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
key(name, &:filled?)
|
53
|
+
end
|
54
|
+
end
|
37
55
|
|
38
|
-
key(
|
39
|
-
key(identifier, &:filled?)
|
56
|
+
key(conf_name, &:filled?)
|
40
57
|
|
41
|
-
rule(
|
58
|
+
rule(conf_name, eql?: [name, conf_name])
|
42
59
|
end
|
43
60
|
|
44
61
|
private
|
@@ -54,3 +71,4 @@ end
|
|
54
71
|
require 'dry/validation/schema/rule'
|
55
72
|
require 'dry/validation/schema/value'
|
56
73
|
require 'dry/validation/schema/key'
|
74
|
+
require 'dry/validation/schema/attr'
|
@@ -6,6 +6,22 @@ module Dry
|
|
6
6
|
class Schema::Form < Schema
|
7
7
|
attr_reader :input_type
|
8
8
|
|
9
|
+
def self.key(name, &block)
|
10
|
+
if block
|
11
|
+
super
|
12
|
+
else
|
13
|
+
super(name, &:filled?)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.optional(name, &block)
|
18
|
+
if block
|
19
|
+
super
|
20
|
+
else
|
21
|
+
super(name, &:filled?)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
9
25
|
def initialize
|
10
26
|
super
|
11
27
|
@input_type = InputTypeCompiler.new.(self.class.rules.map(&:to_ary))
|
@@ -40,11 +40,19 @@ module Dry
|
|
40
40
|
all_msgs = error_compiler.with(options).(errors.map(&:to_ary))
|
41
41
|
hints = hint_compiler.with(options).call
|
42
42
|
|
43
|
-
all_msgs.
|
44
|
-
|
43
|
+
msgs_data = all_msgs.map do |(name, data)|
|
44
|
+
msgs, input =
|
45
|
+
if data.is_a?(Hash)
|
46
|
+
values = data.values
|
47
|
+
[values.map(&:first).flatten.concat(hints[name]).uniq, values[0][1]]
|
48
|
+
else
|
49
|
+
[data[0].concat(hints[name]).uniq.flatten, data[1]]
|
50
|
+
end
|
51
|
+
|
52
|
+
[name, [msgs, input]]
|
45
53
|
end
|
46
54
|
|
47
|
-
|
55
|
+
Hash[msgs_data]
|
48
56
|
end
|
49
57
|
end
|
50
58
|
|
@@ -1,20 +1,38 @@
|
|
1
1
|
module Dry
|
2
2
|
module Validation
|
3
3
|
class Schema
|
4
|
-
class Rule
|
4
|
+
class Rule < BasicObject
|
5
5
|
attr_reader :name, :node
|
6
6
|
|
7
7
|
class Check < Rule
|
8
|
+
def class
|
9
|
+
Schema::Rule::Check
|
10
|
+
end
|
11
|
+
|
8
12
|
def method_missing(meth, *)
|
9
13
|
self.class.new(name, [:check, [name, [:predicate, [name, [meth]]]]])
|
10
14
|
end
|
11
15
|
end
|
12
16
|
|
17
|
+
class Result < Rule
|
18
|
+
def class
|
19
|
+
Schema::Rule::Result
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(meth, *args)
|
23
|
+
self.class.new(name, [:res, [name, [:predicate, [meth, args]]]])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
13
27
|
def initialize(name, node)
|
14
28
|
@name = name
|
15
29
|
@node = node
|
16
30
|
end
|
17
31
|
|
32
|
+
def class
|
33
|
+
Schema::Rule
|
34
|
+
end
|
35
|
+
|
18
36
|
def to_ary
|
19
37
|
node
|
20
38
|
end
|
@@ -24,6 +42,10 @@ module Dry
|
|
24
42
|
Rule::Check.new(name, [:check, [name, [:predicate, [name, []]]]])
|
25
43
|
end
|
26
44
|
|
45
|
+
def is_a?(other)
|
46
|
+
self.class == other
|
47
|
+
end
|
48
|
+
|
27
49
|
def not
|
28
50
|
self.class.new(:"not_#{name}", [:not, node])
|
29
51
|
end
|