dry-validation 0.8.0 → 0.9.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 -1
- data/CHANGELOG.md +39 -1
- data/benchmarks/benchmark_schema_invalid_huge.rb +52 -0
- data/benchmarks/profile_schema_huge_invalid.rb +30 -0
- data/config/errors.yml +3 -2
- data/dry-validation.gemspec +2 -2
- data/lib/dry/validation.rb +20 -32
- data/lib/dry/validation/constants.rb +6 -0
- data/lib/dry/validation/error.rb +5 -2
- data/lib/dry/validation/error_compiler.rb +46 -116
- data/lib/dry/validation/executor.rb +105 -0
- data/lib/dry/validation/hint_compiler.rb +36 -68
- data/lib/dry/validation/message.rb +86 -0
- data/lib/dry/validation/message_compiler.rb +141 -0
- data/lib/dry/validation/message_set.rb +70 -0
- data/lib/dry/validation/messages/abstract.rb +1 -1
- data/lib/dry/validation/messages/i18n.rb +5 -0
- data/lib/dry/validation/predicate_registry.rb +8 -3
- data/lib/dry/validation/result.rb +6 -7
- data/lib/dry/validation/schema.rb +21 -227
- data/lib/dry/validation/schema/check.rb +1 -1
- data/lib/dry/validation/schema/class_interface.rb +193 -0
- data/lib/dry/validation/schema/deprecated.rb +1 -2
- data/lib/dry/validation/schema/key.rb +4 -0
- data/lib/dry/validation/schema/value.rb +12 -7
- data/lib/dry/validation/schema_compiler.rb +20 -1
- data/lib/dry/validation/type_specs.rb +70 -0
- data/lib/dry/validation/version.rb +1 -1
- data/spec/fixtures/locales/pl.yml +1 -1
- data/spec/integration/custom_predicates_spec.rb +37 -0
- data/spec/integration/error_compiler_spec.rb +39 -39
- data/spec/integration/form/predicates/key_spec.rb +10 -18
- data/spec/integration/form/predicates/size/fixed_spec.rb +8 -12
- data/spec/integration/form/predicates/size/range_spec.rb +7 -7
- data/spec/integration/hints_spec.rb +17 -0
- data/spec/integration/messages/i18n_spec.rb +2 -2
- data/spec/integration/schema/check_rules_spec.rb +2 -2
- data/spec/integration/schema/defining_base_schema_spec.rb +38 -0
- data/spec/integration/schema/dynamic_predicate_args_spec.rb +18 -0
- data/spec/integration/schema/macros/each_spec.rb +2 -2
- data/spec/integration/schema/macros/input_spec.rb +102 -10
- data/spec/integration/schema/macros/maybe_spec.rb +30 -0
- data/spec/integration/schema/nested_schemas_spec.rb +200 -0
- data/spec/integration/schema/nested_values_spec.rb +3 -1
- data/spec/integration/schema/option_with_default_spec.rb +54 -20
- data/spec/integration/schema/predicates/size/fixed_spec.rb +10 -10
- data/spec/integration/schema/predicates/size/range_spec.rb +8 -10
- data/spec/unit/error_compiler_spec.rb +1 -1
- data/spec/unit/hint_compiler_spec.rb +2 -2
- metadata +18 -7
- data/examples/rule_ast.rb +0 -25
- data/lib/dry/validation/error_compiler/input.rb +0 -135
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2a8ab72eef1372d4c7e77ca26f239505a4bc8e3
|
4
|
+
data.tar.gz: cdf67dd43d094a74a2e1b7426e465e8a1207de70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 171bc1e3cd30d4b5d6bcb04fbaeaf3d3150a35dd07eadd293f9f4295845caa453e071c342f50fbab1f78d9f877ffb8030a5bf940c65b3a0211a8bcbd91117bdd
|
7
|
+
data.tar.gz: 0ad0b772fffd453b7f948169568fbebc57b8731d4baf18f6be47be330bf36540f2a30d99312af786489e8be633edf08601a8b4fdae3860eacb775f69714c3a40
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,41 @@
|
|
1
|
+
# v0.9.0 2016-07-08
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Support for defining maybe-schemas via `maybe { schema { .. } }` (solnic)
|
6
|
+
* Support for interpolation of custom failure messages for custom rules (solnic)
|
7
|
+
* Support for defining a base schema **class** with config and rules (solnic)
|
8
|
+
* Support for more than 1 predicate in `input` macro (solnic)
|
9
|
+
* Class-level `define!` API for defining rules on a class (solnic)
|
10
|
+
* `:i18n` messages support merging from other paths via `messages_file` setting (solnic)
|
11
|
+
* Support for message token transformations in custom predicates (fran-worley)
|
12
|
+
* [EXPERIMENTAL] Ability to compose predicates that accept dynamic args provided by the schema (solnic)
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
|
16
|
+
* Tokens for `size?` were renamed `left` => `size_left` and `right` => `size_right` (fran-worley)
|
17
|
+
|
18
|
+
### Fixed
|
19
|
+
|
20
|
+
* Duped key names in nested schemas no longer result in invalid error messages structure (solnic)
|
21
|
+
* Error message structure for deeply nested each/schema rules (solnic)
|
22
|
+
* Values from `option` are passed down to nested schemas when using `Schema#with` (solnic)
|
23
|
+
* Hints now work with array elements too (solnic)
|
24
|
+
* Hints for elements are no longer provided for an array when the value is not an array (solnic)
|
25
|
+
* `input` macro no longer messes up error messages for nested structures (solnic)
|
26
|
+
* `messages` and `error_compiler` are now properly inherited from base schema class (solnic)
|
27
|
+
|
28
|
+
### Internal
|
29
|
+
|
30
|
+
* Compiling messages is now ~5% faster (solnic + splattael)
|
31
|
+
* Refactored Error and Hint compilers (solnic)
|
32
|
+
* Refactored Schema to use an internal executor objects with steps (solnic)
|
33
|
+
* Extracted root-rule into a separate validation step (solnic)
|
34
|
+
* Added `MessageSet` that result objects now use (in 1.0.0 it'll be exposed via public API) (solnic)
|
35
|
+
* We can now distinguish error messages from validation hints via `Message` and `Hint` objects (solnic)
|
36
|
+
|
37
|
+
[Compare v0.8.0...master](https://github.com/dryrb/dry-validation/compare/v0.8.0...master)
|
38
|
+
|
1
39
|
# v0.8.0 2016-07-01
|
2
40
|
|
3
41
|
### Added
|
@@ -54,7 +92,7 @@
|
|
54
92
|
* Make pry console optional with IRB as a default (flash-gordon)
|
55
93
|
* Remove wrapping rules in :set nodes (solnic)
|
56
94
|
|
57
|
-
[Compare v0.7.4...
|
95
|
+
[Compare v0.7.4...v0.8.0](https://github.com/dryrb/dry-validation/compare/v0.7.4...v0.8.0)
|
58
96
|
|
59
97
|
# v0.7.4 2016-04-06
|
60
98
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
require 'dry-validation'
|
5
|
+
|
6
|
+
I18n.locale = :en
|
7
|
+
I18n.backend.load_translations
|
8
|
+
|
9
|
+
COUNT = ENV['COUNT'].to_i
|
10
|
+
FIELDS = COUNT.times.map { |i| :"field_#{i}" }
|
11
|
+
|
12
|
+
class User
|
13
|
+
include ActiveModel::Validations
|
14
|
+
|
15
|
+
attr_reader(*FIELDS)
|
16
|
+
validates(*FIELDS, presence: true, numericality: { greater_than: FIELDS.size / 2 })
|
17
|
+
|
18
|
+
def initialize(attrs)
|
19
|
+
attrs.each do |field, value|
|
20
|
+
instance_variable_set(:"@#{field}", value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
schema = Dry::Validation.Schema do
|
26
|
+
configure do
|
27
|
+
config.messages = :i18n
|
28
|
+
end
|
29
|
+
|
30
|
+
FIELDS.each do |field|
|
31
|
+
required(field).value(:int?, gt?: FIELDS.size / 2)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
data = FIELDS.reduce({}) { |h, f| h.update(f => FIELDS.index(f) + 1) }
|
36
|
+
|
37
|
+
puts schema.(data).inspect
|
38
|
+
puts User.new(data).validate
|
39
|
+
|
40
|
+
Benchmark.ips do |x|
|
41
|
+
x.report('ActiveModel::Validations') do
|
42
|
+
user = User.new(data)
|
43
|
+
user.validate
|
44
|
+
user.errors
|
45
|
+
end
|
46
|
+
|
47
|
+
x.report('dry-validation / schema') do
|
48
|
+
schema.(data).messages
|
49
|
+
end
|
50
|
+
|
51
|
+
x.compare!
|
52
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'suite'
|
2
|
+
require 'hotch'
|
3
|
+
|
4
|
+
require 'dry-validation'
|
5
|
+
|
6
|
+
I18n.locale = :en
|
7
|
+
I18n.backend.load_translations
|
8
|
+
|
9
|
+
COUNT = ENV['COUNT'].to_i
|
10
|
+
FIELDS = COUNT.times.map { |i| :"field_#{i}" }
|
11
|
+
|
12
|
+
schema = Dry::Validation.Schema do
|
13
|
+
configure do
|
14
|
+
config.messages = :i18n
|
15
|
+
end
|
16
|
+
|
17
|
+
FIELDS.each do |field|
|
18
|
+
required(field).filled(gt?: FIELDS.size / 2)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
data = FIELDS.reduce({}) { |h, f| h.update(f => FIELDS.index(f) + 1) }
|
23
|
+
|
24
|
+
puts schema.(data).inspect
|
25
|
+
|
26
|
+
Hotch() do
|
27
|
+
100.times do
|
28
|
+
schema.(data).messages
|
29
|
+
end
|
30
|
+
end
|
data/config/errors.yml
CHANGED
@@ -7,6 +7,7 @@ en:
|
|
7
7
|
excludes?: "must not include %{value}"
|
8
8
|
|
9
9
|
excluded_from?: "must not be one of: %{list}"
|
10
|
+
exclusion?: "must not be one of: %{list}"
|
10
11
|
|
11
12
|
eql?: "must be equal to %{left}"
|
12
13
|
|
@@ -72,10 +73,10 @@ en:
|
|
72
73
|
size?:
|
73
74
|
arg:
|
74
75
|
default: "size must be %{size}"
|
75
|
-
range: "size must be within %{
|
76
|
+
range: "size must be within %{size_left} - %{size_right}"
|
76
77
|
|
77
78
|
value:
|
78
79
|
string:
|
79
80
|
arg:
|
80
81
|
default: "length must be %{size}"
|
81
|
-
range: "length must be within %{
|
82
|
+
range: "length must be within %{size_left} - %{size_right}"
|
data/dry-validation.gemspec
CHANGED
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.homepage = 'https://github.com/dryrb/dry-validation'
|
11
11
|
spec.license = 'MIT'
|
12
12
|
|
13
|
-
spec.files = `git ls-files -z`.split("\x0")
|
14
|
-
spec.executables =
|
13
|
+
spec.files = `git ls-files -z`.split("\x0") - ['bin/console']
|
14
|
+
spec.executables = []
|
15
15
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
16
|
spec.require_paths = ['lib']
|
17
17
|
|
data/lib/dry/validation.rb
CHANGED
@@ -7,6 +7,25 @@ require 'dry/validation/schema/form'
|
|
7
7
|
require 'dry/validation/schema/json'
|
8
8
|
|
9
9
|
module Dry
|
10
|
+
# FIXME: move this to dry-logic if it works lol
|
11
|
+
require 'dry/logic/predicate'
|
12
|
+
module Logic
|
13
|
+
class Predicate
|
14
|
+
class Curried < Predicate
|
15
|
+
def evaluate_args!(schema)
|
16
|
+
@args = args.map { |arg|
|
17
|
+
arg.is_a?(UnboundMethod) ? arg.bind(schema).() : arg
|
18
|
+
}
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def evaluate_args!(*)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
10
29
|
module Validation
|
11
30
|
MissingMessageError = Class.new(StandardError)
|
12
31
|
InvalidSchemaError = Class.new(StandardError)
|
@@ -17,38 +36,7 @@ module Dry
|
|
17
36
|
|
18
37
|
def self.Schema(base = Schema, **options, &block)
|
19
38
|
schema_class = Class.new(base.is_a?(Schema) ? base.class : base)
|
20
|
-
|
21
|
-
dsl_opts = {
|
22
|
-
schema_class: schema_class,
|
23
|
-
registry: schema_class.registry,
|
24
|
-
parent: options[:parent]
|
25
|
-
}
|
26
|
-
|
27
|
-
dsl_ext = schema_class.config.dsl_extensions
|
28
|
-
|
29
|
-
dsl = Schema::Value.new(dsl_opts)
|
30
|
-
dsl_ext.__send__(:extend_object, dsl) if dsl_ext
|
31
|
-
dsl.predicates(options[:predicates]) if options.key?(:predicates)
|
32
|
-
dsl.instance_exec(&block) if block
|
33
|
-
|
34
|
-
klass = dsl.schema_class
|
35
|
-
|
36
|
-
base_rules = klass.config.rules + (options.fetch(:rules, []) + dsl.rules)
|
37
|
-
|
38
|
-
rules =
|
39
|
-
if klass.config.input
|
40
|
-
input_rule = dsl.__send__(klass.config.input)
|
41
|
-
[input_rule.and(dsl.with(rules: base_rules))]
|
42
|
-
else
|
43
|
-
base_rules
|
44
|
-
end
|
45
|
-
|
46
|
-
klass.configure do |config|
|
47
|
-
config.rules = rules
|
48
|
-
config.checks = config.checks + dsl.checks
|
49
|
-
config.path = dsl.path
|
50
|
-
config.type_map = klass.build_type_map(dsl.type_map) if config.type_specs
|
51
|
-
end
|
39
|
+
klass = schema_class.define(options.merge(schema_class: schema_class), &block)
|
52
40
|
|
53
41
|
if options[:build] == false
|
54
42
|
klass
|
data/lib/dry/validation/error.rb
CHANGED
@@ -1,147 +1,77 @@
|
|
1
|
+
require 'dry/validation/message_compiler'
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Validation
|
3
|
-
class ErrorCompiler
|
4
|
-
|
5
|
-
|
6
|
-
DEFAULT_RESULT = {}.freeze
|
7
|
-
EMPTY_HINTS = [].freeze
|
8
|
-
KEY_SEPARATOR = '.'.freeze
|
9
|
-
|
10
|
-
def initialize(messages, options = {})
|
11
|
-
@messages = messages
|
12
|
-
@options = Hash[options]
|
13
|
-
@hints = @options.fetch(:hints, DEFAULT_RESULT)
|
14
|
-
@full = options.fetch(:full, false)
|
5
|
+
class ErrorCompiler < MessageCompiler
|
6
|
+
def message_type
|
7
|
+
:failure
|
15
8
|
end
|
16
9
|
|
17
|
-
def
|
18
|
-
|
10
|
+
def message_class
|
11
|
+
Message
|
19
12
|
end
|
20
13
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
14
|
+
def visit_error(node, opts = EMPTY_HASH)
|
15
|
+
rule, error = node
|
16
|
+
node_path = Array(opts.fetch(:path, rule))
|
24
17
|
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
path = if rule.is_a?(Array) && rule.size > node_path.size
|
19
|
+
rule
|
20
|
+
else
|
21
|
+
node_path
|
22
|
+
end
|
28
23
|
|
29
|
-
|
30
|
-
__send__(:"visit_#{node[0]}", node[1], *args)
|
31
|
-
end
|
24
|
+
path.compact!
|
32
25
|
|
33
|
-
|
34
|
-
visit_error(node[1], true)
|
35
|
-
end
|
26
|
+
template = messages[rule, default_lookup_options]
|
36
27
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def visit_error(error, schema = false)
|
42
|
-
name, other = error
|
43
|
-
message = messages[name]
|
44
|
-
|
45
|
-
if message
|
46
|
-
{ name => [message] }
|
28
|
+
if template
|
29
|
+
predicate, args, tokens = visit(error, opts.merge(path: path, message: false))
|
30
|
+
message_class[predicate, path, template % tokens, rule: rule, args: args]
|
47
31
|
else
|
48
|
-
|
49
|
-
|
50
|
-
if result.is_a?(Array)
|
51
|
-
merge(result)
|
52
|
-
elsif !schema
|
53
|
-
merge_hints(result)
|
54
|
-
elsif schema
|
55
|
-
merge_hints(result, hints[schema] || DEFAULT_RESULT)
|
56
|
-
else
|
57
|
-
result
|
58
|
-
end
|
32
|
+
visit(error, opts.merge(rule: rule, path: path))
|
59
33
|
end
|
60
34
|
end
|
61
35
|
|
62
|
-
def visit_input(node,
|
63
|
-
|
64
|
-
|
65
|
-
end
|
36
|
+
def visit_input(node, opts = EMPTY_HASH)
|
37
|
+
rule, result = node
|
38
|
+
opt_rule = opts[:rule]
|
66
39
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
def visit_implication(node)
|
73
|
-
_, right = node
|
74
|
-
visit(right)
|
75
|
-
end
|
76
|
-
|
77
|
-
def visit_key(rule)
|
78
|
-
_, predicate = rule
|
79
|
-
visit(predicate)
|
80
|
-
end
|
81
|
-
|
82
|
-
def visit_attr(rule)
|
83
|
-
_, predicate = rule
|
84
|
-
visit(predicate)
|
40
|
+
if opts[:each] && opt_rule.is_a?(Array)
|
41
|
+
visit(result, opts.merge(rule: rule, path: opts[:path] + [opt_rule.last]))
|
42
|
+
else
|
43
|
+
visit(result, opts.merge(rule: rule))
|
44
|
+
end
|
85
45
|
end
|
86
46
|
|
87
|
-
def
|
88
|
-
|
47
|
+
def visit_result(node, opts = EMPTY_HASH)
|
48
|
+
input, other = node
|
49
|
+
visit(other, opts.merge(input: input))
|
89
50
|
end
|
90
51
|
|
91
|
-
def
|
92
|
-
|
93
|
-
res[key] =
|
94
|
-
case val
|
95
|
-
when Hash then dump_messages(val)
|
96
|
-
when Array then val.map(&:to_s)
|
97
|
-
end
|
98
|
-
end
|
52
|
+
def visit_each(node, opts = EMPTY_HASH)
|
53
|
+
node.map { |el| visit(el, opts.merge(each: true)) }
|
99
54
|
end
|
100
55
|
|
101
|
-
|
102
|
-
|
103
|
-
def merge_hints(messages, hints = self.hints)
|
104
|
-
messages.each_with_object({}) do |(name, msgs), res|
|
105
|
-
res[name] =
|
106
|
-
if msgs.is_a?(Hash)
|
107
|
-
res[name] = merge_hints(msgs, hints)
|
108
|
-
else
|
109
|
-
all_hints = (hints[name] || EMPTY_HINTS)
|
56
|
+
def visit_schema(node, opts = EMPTY_HASH)
|
57
|
+
path, other = node
|
110
58
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
msgs
|
117
|
-
end
|
118
|
-
end
|
59
|
+
if opts[:path]
|
60
|
+
opts[:path] << path.last
|
61
|
+
visit(other, opts)
|
62
|
+
else
|
63
|
+
visit(other, opts.merge(path: [path]))
|
119
64
|
end
|
120
65
|
end
|
121
66
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
def merge(result)
|
127
|
-
result.reduce { |a, e| deep_merge(a, e) } || DEFAULT_RESULT
|
67
|
+
def visit_check(node, opts = EMPTY_HASH)
|
68
|
+
path, other = node
|
69
|
+
visit(other, opts.merge(path: Array(path)))
|
128
70
|
end
|
129
71
|
|
130
|
-
def
|
131
|
-
|
132
|
-
if a.is_a?(Hash)
|
133
|
-
deep_merge(a, e)
|
134
|
-
else
|
135
|
-
a + e
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def input_visitor(name, input)
|
141
|
-
Input.new(messages, options.merge(name: name, input: input))
|
72
|
+
def lookup_options(opts, arg_vals = [])
|
73
|
+
super.update(val_type: opts[:input].class)
|
142
74
|
end
|
143
75
|
end
|
144
76
|
end
|
145
77
|
end
|
146
|
-
|
147
|
-
require 'dry/validation/error_compiler/input'
|