dry-validation 0.8.0 → 0.9.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 -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'
|