dry-validation 1.0.0.rc1 → 1.0.0.rc3
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/CHANGELOG.md +33 -0
- data/README.md +2 -3
- data/config/errors.yml +4 -0
- data/lib/dry/validation.rb +45 -1
- data/lib/dry/validation/config.rb +18 -0
- data/lib/dry/validation/constants.rb +3 -0
- data/lib/dry/validation/contract.rb +33 -6
- data/lib/dry/validation/contract/class_interface.rb +65 -5
- data/lib/dry/validation/evaluator.rb +24 -5
- data/lib/dry/validation/extensions/hints.rb +15 -7
- data/lib/dry/validation/macros.rb +73 -0
- data/lib/dry/validation/message.rb +30 -7
- data/lib/dry/validation/message_set.rb +12 -9
- data/lib/dry/validation/messages/resolver.rb +4 -0
- data/lib/dry/validation/result.rb +41 -18
- data/lib/dry/validation/rule.rb +34 -7
- data/lib/dry/validation/values.rb +62 -0
- data/lib/dry/validation/version.rb +1 -1
- metadata +7 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a644e1a1909528377d2edf6acb339bb6e571a9c2959700f9537ed849c9b6c0bc
|
4
|
+
data.tar.gz: 22bce26482e71124a4c83c4c9d1a6dd2da2960d02b30080ff672540322247d42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37ddca21e417d6f4d8a6f16c10aecfb8a26f50483115d286f77ce84420ada5b67f0369d7a877d9ca7bc8d717f2ff2f8b770ffdfe66e370e570a4ab9abc7e6f38
|
7
|
+
data.tar.gz: 77bcd57cbc2918991363c3cdda5302528c10370a50c1d684462e15967812643008da5129168600f41af592c31831c712c69558997c2f470bc2f8b62ca0a3c806
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,36 @@
|
|
1
|
+
# v1.0.0.rc3 2019-05-06
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [EXPERIMENTAL] `Validation.register_macro` for registering global macros (solnic)
|
6
|
+
* [EXPERIMENTAL] `Contract.register_macro` for registering macros available to specific contract classes (solnic)
|
7
|
+
* `Dry::Validation.Contract` shortcut for quickly defining a contract and getting its instance back (solnic)
|
8
|
+
* New configuration option `config.locale` for setting the default locale (solnic)
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
* `config/errors.yml` are now bundled with the gem, **`rc2` was broken because of this** (solnic)
|
13
|
+
|
14
|
+
[Compare v1.0.0.rc2...v1.0.0.rc3](https://github.com/dry-rb/dry-validation/compare/v1.0.0.rc2...v1.0.0.rc3)
|
15
|
+
|
16
|
+
# v1.0.0.rc2 2019-05-04
|
17
|
+
|
18
|
+
### Added
|
19
|
+
|
20
|
+
* [EXPERIMENTAL] support for registering macros via `Dry::Validation::Macros.register(:your_macro, &block)` (solnic)
|
21
|
+
* [EXPERIMENTAL] `:acceptance` as the first built-in macro (issue #157) (solnic)
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
* Passing invalid argument to `failure` will raise a meaningful error instead of crashing (solnic)
|
26
|
+
|
27
|
+
### Changed
|
28
|
+
|
29
|
+
* In rule validation blocks, `values` is now an instance of a hash-like `Dry::Validation::Values` class, rather than `Dry::Schema::Result`. This gives more convenient access to data within rules (solnic)
|
30
|
+
* Dependency on `dry-schema` was updated to `~> 1.0` (solnic)
|
31
|
+
|
32
|
+
[Compare v1.0.0.rc1...v1.0.0.rc2](https://github.com/dry-rb/dry-validation/compare/v1.0.0.rc1...v1.0.0.rc2)
|
33
|
+
|
1
34
|
# v1.0.0.rc1 2019-04-26
|
2
35
|
|
3
36
|
### Added
|
data/README.md
CHANGED
@@ -14,7 +14,8 @@
|
|
14
14
|
|
15
15
|
## Links
|
16
16
|
|
17
|
-
* [
|
17
|
+
* [User documentation](https://dry-rb.org/gems/dry-validation)
|
18
|
+
* [API documentation](http://rubydoc.info/gems/dry-validation)
|
18
19
|
* [Guidelines for contributing](CONTRIBUTING.md)
|
19
20
|
|
20
21
|
## Supported Ruby versions
|
@@ -24,8 +25,6 @@ This library officially supports following Ruby versions:
|
|
24
25
|
* MRI >= `2.4`
|
25
26
|
* jruby >= `9.2`
|
26
27
|
|
27
|
-
It **should** work on MRI `2.3.x` too, but there's no official support for this version.
|
28
|
-
|
29
28
|
## License
|
30
29
|
|
31
30
|
See `LICENSE` file.
|
data/config/errors.yml
ADDED
data/lib/dry/validation.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'dry/validation/constants'
|
3
4
|
require 'dry/validation/contract'
|
5
|
+
require 'dry/validation/macros'
|
4
6
|
|
7
|
+
# Main namespace
|
8
|
+
#
|
9
|
+
# @api public
|
5
10
|
module Dry
|
6
11
|
# Main library namespace
|
7
12
|
#
|
8
|
-
# @api
|
13
|
+
# @api public
|
9
14
|
module Validation
|
10
15
|
extend Dry::Core::Extensions
|
11
16
|
|
@@ -16,5 +21,44 @@ module Dry
|
|
16
21
|
register_extension(:hints) do
|
17
22
|
require 'dry/validation/extensions/hints'
|
18
23
|
end
|
24
|
+
|
25
|
+
# Register a new global macro
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# Dry::Validation.register_macro(:even_numbers) do
|
29
|
+
# key.failure('all numbers must be even') unless values[key_name].all?(&:even?)
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @param [Symbol] name The name of the macro
|
33
|
+
#
|
34
|
+
# @return [self]
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def self.register_macro(name, &block)
|
38
|
+
Macros.register(name, &block)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Define a contract and build its instance
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# my_contract = Dry::Validation.Contract do
|
46
|
+
# params do
|
47
|
+
# required(:name).filled(:string)
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# my_contract.call(name: "Jane")
|
52
|
+
#
|
53
|
+
# @param [Hash] options Contract options
|
54
|
+
#
|
55
|
+
# @see Contract
|
56
|
+
#
|
57
|
+
# @return [Contract]
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
def self.Contract(options = EMPTY_HASH, &block)
|
61
|
+
Contract.build(options, &block)
|
62
|
+
end
|
19
63
|
end
|
20
64
|
end
|
@@ -1,10 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'dry/schema/config'
|
4
|
+
require 'dry/validation/macros'
|
4
5
|
|
5
6
|
module Dry
|
6
7
|
module Validation
|
8
|
+
# Configuration for contracts
|
9
|
+
#
|
10
|
+
# @see Contract#config
|
11
|
+
#
|
12
|
+
# @api public
|
7
13
|
class Config < Schema::Config
|
14
|
+
setting :locale, :en
|
15
|
+
setting :macros, Macros::Container.new, &:dup
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
def macros
|
19
|
+
config.macros
|
20
|
+
end
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
def locale
|
24
|
+
config.locale
|
25
|
+
end
|
8
26
|
end
|
9
27
|
end
|
10
28
|
end
|
@@ -8,7 +8,10 @@ module Dry
|
|
8
8
|
|
9
9
|
DOT = '.'
|
10
10
|
|
11
|
+
# Error raised when a localized message was not found
|
11
12
|
MissingMessageError = Class.new(StandardError)
|
13
|
+
|
14
|
+
# Error raised when trying to define a schema in a contract class that already has a schema
|
12
15
|
DuplicateSchemaError = Class.new(StandardError)
|
13
16
|
end
|
14
17
|
end
|
@@ -4,6 +4,7 @@ require 'concurrent/map'
|
|
4
4
|
|
5
5
|
require 'dry/equalizer'
|
6
6
|
require 'dry/initializer'
|
7
|
+
require 'dry/schema/path'
|
7
8
|
|
8
9
|
require 'dry/validation/config'
|
9
10
|
require 'dry/validation/constants'
|
@@ -54,16 +55,23 @@ module Dry
|
|
54
55
|
extend ClassInterface
|
55
56
|
|
56
57
|
config.messages.top_namespace = 'dry_validation'
|
58
|
+
config.messages.load_paths << Pathname(__FILE__).join('../../../../config/errors.yml').realpath
|
57
59
|
|
58
60
|
# @!attribute [r] config
|
59
|
-
# @return [Config]
|
61
|
+
# @return [Config] Contract's configuration object
|
60
62
|
# @api public
|
61
63
|
option :config, default: -> { self.class.config }
|
62
64
|
|
63
65
|
# @!attribute [r] locale
|
64
|
-
# @return [Symbol]
|
66
|
+
# @return [Symbol] Contract's locale (default is `:en`)
|
65
67
|
# @api public
|
66
|
-
option :locale, default: -> {
|
68
|
+
option :locale, default: -> { self.class.config.locale }
|
69
|
+
|
70
|
+
# @!attribute [r] macros
|
71
|
+
# @return [Macros::Container] Configured macros
|
72
|
+
# @see Macros::Container#register
|
73
|
+
# @api public
|
74
|
+
option :macros, default: -> { config.macros }
|
67
75
|
|
68
76
|
# @!attribute [r] schema
|
69
77
|
# @return [Dry::Schema::Params, Dry::Schema::JSON, Dry::Schema::Processor]
|
@@ -80,7 +88,9 @@ module Dry
|
|
80
88
|
# @api private
|
81
89
|
option :message_resolver, default: -> { Messages::Resolver.new(self.class.messages, locale) }
|
82
90
|
|
83
|
-
# Apply contract to an input
|
91
|
+
# Apply the contract to an input
|
92
|
+
#
|
93
|
+
# @param [Hash] input The input to validate
|
84
94
|
#
|
85
95
|
# @return [Result]
|
86
96
|
#
|
@@ -88,9 +98,9 @@ module Dry
|
|
88
98
|
def call(input)
|
89
99
|
Result.new(schema.(input), Concurrent::Map.new, locale: locale) do |result|
|
90
100
|
rules.each do |rule|
|
91
|
-
next if rule.keys.any? { |key|
|
101
|
+
next if rule.keys.any? { |key| error?(result, key) }
|
92
102
|
|
93
|
-
rule.(self, result
|
103
|
+
rule.(self, result).failures.each do |failure|
|
94
104
|
result.add_error(message_resolver[failure])
|
95
105
|
end
|
96
106
|
end
|
@@ -105,6 +115,23 @@ module Dry
|
|
105
115
|
def inspect
|
106
116
|
%(#<#{self.class} schema=#{schema.inspect} rules=#{rules.inspect}>)
|
107
117
|
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# @api private
|
122
|
+
def error?(result, key)
|
123
|
+
path = Schema::Path[key]
|
124
|
+
result.error?(path) || path.map.with_index { |k, i| result.error?(path.keys[0..i-2]) }.any?
|
125
|
+
end
|
126
|
+
|
127
|
+
# Get a registered macro
|
128
|
+
#
|
129
|
+
# @return [Proc,#to_proc]
|
130
|
+
#
|
131
|
+
# @api private
|
132
|
+
def macro(name)
|
133
|
+
macros.key?(name) ? macros[name] : Macros[name]
|
134
|
+
end
|
108
135
|
end
|
109
136
|
end
|
110
137
|
end
|
@@ -10,6 +10,8 @@ module Dry
|
|
10
10
|
class Contract
|
11
11
|
# Contract's class interface
|
12
12
|
#
|
13
|
+
# @see Contract
|
14
|
+
#
|
13
15
|
# @api public
|
14
16
|
module ClassInterface
|
15
17
|
# @api private
|
@@ -20,6 +22,11 @@ module Dry
|
|
20
22
|
|
21
23
|
# Configuration
|
22
24
|
#
|
25
|
+
# @example
|
26
|
+
# class MyContract < Dry::Validation::Contract
|
27
|
+
# config.messages.backend = :i18n
|
28
|
+
# end
|
29
|
+
#
|
23
30
|
# @return [Config]
|
24
31
|
#
|
25
32
|
# @api public
|
@@ -27,11 +34,42 @@ module Dry
|
|
27
34
|
@config ||= Validation::Config.new
|
28
35
|
end
|
29
36
|
|
37
|
+
# Return macros registered for this class
|
38
|
+
#
|
39
|
+
# @return [Macros::Container]
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def macros
|
43
|
+
config.macros
|
44
|
+
end
|
45
|
+
|
46
|
+
# Register a new global macro
|
47
|
+
#
|
48
|
+
# Macros will be available for the contract class and its descendants
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# class MyContract < Dry::Validation::Contract
|
52
|
+
# register_macro(:even_numbers) do
|
53
|
+
# key.failure('all numbers must be even') unless values[key_name].all?(&:even?)
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# @param [Symbol] name The name of the macro
|
58
|
+
#
|
59
|
+
# @return [self]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def register_macro(name, &block)
|
63
|
+
macros.register(name, &block)
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
30
67
|
# Define a params schema for your contract
|
31
68
|
#
|
32
69
|
# This type of schema is suitable for HTTP parameters
|
33
70
|
#
|
34
71
|
# @return [Dry::Schema::Params]
|
72
|
+
# @see https://dry-rb.org/gems/dry-schema/params/
|
35
73
|
#
|
36
74
|
# @api public
|
37
75
|
def params(&block)
|
@@ -43,6 +81,7 @@ module Dry
|
|
43
81
|
# This type of schema is suitable for JSON data
|
44
82
|
#
|
45
83
|
# @return [Dry::Schema::JSON]
|
84
|
+
# @see https://dry-rb.org/gems/dry-schema/json/
|
46
85
|
#
|
47
86
|
# @api public
|
48
87
|
def json(&block)
|
@@ -54,6 +93,7 @@ module Dry
|
|
54
93
|
# This type of schema does not offer coercion out of the box
|
55
94
|
#
|
56
95
|
# @return [Dry::Schema::Processor]
|
96
|
+
# @see https://dry-rb.org/gems/dry-schema/
|
57
97
|
#
|
58
98
|
# @api public
|
59
99
|
def schema(&block)
|
@@ -72,19 +112,31 @@ module Dry
|
|
72
112
|
# failure('please provide a valid street address') if valid_street?(values[:street])
|
73
113
|
# end
|
74
114
|
#
|
75
|
-
# @return [
|
115
|
+
# @return [Rule]
|
76
116
|
#
|
77
117
|
# @api public
|
78
118
|
def rule(*keys, &block)
|
79
|
-
|
80
|
-
|
119
|
+
Rule.new(keys: keys, block: block).tap do |rule|
|
120
|
+
rules << rule
|
121
|
+
end
|
81
122
|
end
|
82
123
|
|
83
124
|
# A shortcut that can be used to define contracts that won't be reused or inherited
|
84
125
|
#
|
126
|
+
# @example
|
127
|
+
# my_contract = Dry::Validation::Contract.build do
|
128
|
+
# params do
|
129
|
+
# required(:name).filled(:string)
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# my_contract.call(name: "Jane")
|
134
|
+
#
|
135
|
+
# @return [Contract]
|
136
|
+
#
|
85
137
|
# @api public
|
86
|
-
def build(
|
87
|
-
Class.new(self, &block).new(
|
138
|
+
def build(options = EMPTY_HASH, &block)
|
139
|
+
Class.new(self, &block).new(options)
|
88
140
|
end
|
89
141
|
|
90
142
|
# @api private
|
@@ -92,6 +144,10 @@ module Dry
|
|
92
144
|
@__schema__ if defined?(@__schema__)
|
93
145
|
end
|
94
146
|
|
147
|
+
# Return rules defined in this class
|
148
|
+
#
|
149
|
+
# @return [Array<Rule>]
|
150
|
+
#
|
95
151
|
# @api private
|
96
152
|
def rules
|
97
153
|
@rules ||= EMPTY_ARRAY
|
@@ -99,6 +155,10 @@ module Dry
|
|
99
155
|
.concat(superclass.respond_to?(:rules) ? superclass.rules : EMPTY_ARRAY)
|
100
156
|
end
|
101
157
|
|
158
|
+
# Return messages configured for this class
|
159
|
+
#
|
160
|
+
# @return [Dry::Schema::Messages]
|
161
|
+
#
|
102
162
|
# @api private
|
103
163
|
def messages
|
104
164
|
@messages ||= Schema::Messages.setup(config.messages)
|
@@ -11,7 +11,7 @@ module Dry
|
|
11
11
|
# method calls to the contracts, so that you can use your contract
|
12
12
|
# methods within rule blocks
|
13
13
|
#
|
14
|
-
# @api
|
14
|
+
# @api public
|
15
15
|
class Evaluator
|
16
16
|
extend Dry::Initializer
|
17
17
|
|
@@ -64,9 +64,14 @@ module Dry
|
|
64
64
|
# @api private
|
65
65
|
option :keys
|
66
66
|
|
67
|
+
# @!attribute [r] macros
|
68
|
+
# @return [Array<Symbol>]
|
69
|
+
# @api private
|
70
|
+
option :macros, optional: true, default: proc { EMPTY_ARRAY.dup }
|
71
|
+
|
67
72
|
# @!attribute [r] _context
|
68
73
|
# @return [Concurrent::Map]
|
69
|
-
# @api
|
74
|
+
# @api private
|
70
75
|
option :_context
|
71
76
|
|
72
77
|
# @!attribute [r] path
|
@@ -84,10 +89,15 @@ module Dry
|
|
84
89
|
# @api private
|
85
90
|
def initialize(*args, &block)
|
86
91
|
super(*args)
|
87
|
-
|
92
|
+
|
93
|
+
instance_exec(_context, &block) if block
|
94
|
+
|
95
|
+
macros.each do |macro|
|
96
|
+
instance_exec(_context, ¯o(macro))
|
97
|
+
end
|
88
98
|
end
|
89
99
|
|
90
|
-
# Get
|
100
|
+
# Get `Failures` object for the default or provided path
|
91
101
|
#
|
92
102
|
# @param [Symbol,String,Hash,Array<Symbol>] path
|
93
103
|
#
|
@@ -100,7 +110,7 @@ module Dry
|
|
100
110
|
(@key ||= EMPTY_HASH.dup)[path] ||= Failures.new(path)
|
101
111
|
end
|
102
112
|
|
103
|
-
# Get
|
113
|
+
# Get `Failures` object for base errors
|
104
114
|
#
|
105
115
|
# @return [Failures]
|
106
116
|
#
|
@@ -123,6 +133,15 @@ module Dry
|
|
123
133
|
failures
|
124
134
|
end
|
125
135
|
|
136
|
+
# Return default (first) key name
|
137
|
+
#
|
138
|
+
# @return [Symbol]
|
139
|
+
#
|
140
|
+
# @api public
|
141
|
+
def key_name
|
142
|
+
@key_name ||= keys.first
|
143
|
+
end
|
144
|
+
|
126
145
|
# @api private
|
127
146
|
def respond_to_missing?(meth, include_private = false)
|
128
147
|
super || _contract.respond_to?(meth, true)
|
@@ -4,7 +4,7 @@ require 'dry/monads/result'
|
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
module Validation
|
7
|
-
# Hints extension
|
7
|
+
# Hints extension
|
8
8
|
#
|
9
9
|
# @example
|
10
10
|
# Dry::Validation.load_extensions(:hints)
|
@@ -23,33 +23,41 @@ module Dry
|
|
23
23
|
#
|
24
24
|
# @api public
|
25
25
|
module Hints
|
26
|
+
# Hints extensions for Result
|
27
|
+
#
|
28
|
+
# @api public
|
26
29
|
module ResultExtensions
|
27
30
|
# Return error messages excluding hints
|
28
31
|
#
|
32
|
+
# @macro errors-options
|
29
33
|
# @return [MessageSet]
|
30
34
|
#
|
31
35
|
# @api public
|
32
|
-
def errors(
|
33
|
-
opts =
|
36
|
+
def errors(new_options = EMPTY_HASH)
|
37
|
+
opts = new_options.merge(hints: false)
|
34
38
|
@errors.with(schema_errors(opts), opts)
|
35
39
|
end
|
36
40
|
|
37
41
|
# Return errors and hints
|
38
42
|
#
|
43
|
+
# @macro errors-options
|
44
|
+
#
|
39
45
|
# @return [MessageSet]
|
40
46
|
#
|
41
47
|
# @api public
|
42
|
-
def messages(
|
43
|
-
errors.with(hints.to_a, options.merge(**
|
48
|
+
def messages(new_options = EMPTY_HASH)
|
49
|
+
errors.with(hints.to_a, options.merge(**new_options))
|
44
50
|
end
|
45
51
|
|
46
52
|
# Return hint messages
|
47
53
|
#
|
54
|
+
# @macro errors-options
|
55
|
+
#
|
48
56
|
# @return [MessageSet]
|
49
57
|
#
|
50
58
|
# @api public
|
51
|
-
def hints(
|
52
|
-
|
59
|
+
def hints(new_options = EMPTY_HASH)
|
60
|
+
schema_result.hints(new_options)
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/container'
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Validation
|
7
|
+
# API for registering and accessing Rule macros
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
module Macros
|
11
|
+
# Registry for macros
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
class Container
|
15
|
+
include Dry::Container::Mixin
|
16
|
+
|
17
|
+
# Register a new macro
|
18
|
+
#
|
19
|
+
# @example in a contract class
|
20
|
+
# class MyContract < Dry::Validation::Contract
|
21
|
+
# register_macro(:even_numbers) do
|
22
|
+
# key.failure('all numbers must be even') unless values[key_name].all?(&:even?)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @param [Symbol] name The name of the macro
|
27
|
+
#
|
28
|
+
# @return [self]
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def register(name, &block)
|
32
|
+
super(name, block, call: false)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return a registered macro
|
38
|
+
#
|
39
|
+
# @param [Symbol] name The name of the macro
|
40
|
+
#
|
41
|
+
# @return [Proc]
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def self.[](name)
|
45
|
+
container[name]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Register a global macro
|
49
|
+
#
|
50
|
+
# @see Container#register
|
51
|
+
#
|
52
|
+
# @return [Macros]
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def self.register(*args, &block)
|
56
|
+
container.register(*args, &block)
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# @api private
|
61
|
+
def self.container
|
62
|
+
@container ||= Container.new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Acceptance macro
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
Macros.register(:acceptance) do
|
70
|
+
key.failure(:acceptance, key: key_name) unless values[key_name].equal?(true)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -13,20 +13,43 @@ module Dry
|
|
13
13
|
class Message < Schema::Message
|
14
14
|
include Dry::Equalizer(:text, :path, :meta)
|
15
15
|
|
16
|
-
#
|
17
|
-
#
|
16
|
+
# The error message text
|
17
|
+
#
|
18
|
+
# @return [String] text
|
19
|
+
#
|
20
|
+
# @api public
|
18
21
|
attr_reader :text
|
19
22
|
|
20
|
-
#
|
21
|
-
#
|
23
|
+
# The path to the value with the error
|
24
|
+
#
|
25
|
+
# @return [Array<Symbol, Integer>]
|
26
|
+
#
|
27
|
+
# @api public
|
22
28
|
attr_reader :path
|
23
29
|
|
24
|
-
#
|
25
|
-
#
|
30
|
+
# Optional hash with meta-data
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
#
|
34
|
+
# @api public
|
26
35
|
attr_reader :meta
|
27
36
|
|
37
|
+
# A localized message type
|
38
|
+
#
|
39
|
+
# Localized messsages can be translated to other languages at run-time
|
40
|
+
#
|
28
41
|
# @api public
|
29
42
|
class Localized < Message
|
43
|
+
# Evaluate message text using provided locale
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# result.errors[:email].evaluate(locale: :en, full: true)
|
47
|
+
# # "email is invalid"
|
48
|
+
#
|
49
|
+
# @param [Hash] opts
|
50
|
+
# @option opts [Symbol] :locale Which locale to use
|
51
|
+
# @option opts [Boolean] :full Whether message text should include the key name
|
52
|
+
#
|
30
53
|
# @api public
|
31
54
|
def evaluate(**opts)
|
32
55
|
evaluated_text, rest = text.(opts)
|
@@ -38,7 +61,7 @@ module Dry
|
|
38
61
|
#
|
39
62
|
# @return [Message, Message::Localized]
|
40
63
|
#
|
41
|
-
# @api
|
64
|
+
# @api private
|
42
65
|
def self.[](text, path, meta)
|
43
66
|
klass = text.respond_to?(:call) ? Localized : Message
|
44
67
|
klass.new(text, path: path, meta: meta)
|
@@ -11,15 +11,18 @@ module Dry
|
|
11
11
|
#
|
12
12
|
# @api public
|
13
13
|
class MessageSet < Schema::MessageSet
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
14
|
+
# Return the source set of messages used to produce final evaluated messages
|
15
|
+
#
|
16
|
+
# @return [Array<Message, Message::Localized, Schema::Message>]
|
17
|
+
#
|
18
|
+
# @api private
|
18
19
|
attr_reader :source_messages
|
19
20
|
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# Configured locale
|
22
|
+
#
|
23
|
+
# @return [Symbol]
|
24
|
+
#
|
25
|
+
# @api public
|
23
26
|
attr_reader :locale
|
24
27
|
|
25
28
|
# @api private
|
@@ -64,10 +67,10 @@ module Dry
|
|
64
67
|
# may need.
|
65
68
|
#
|
66
69
|
# @example get a list of base messages
|
67
|
-
# message_set = contract.(input).
|
70
|
+
# message_set = contract.(input).errors
|
68
71
|
# message_set.filter(:base?)
|
69
72
|
#
|
70
|
-
# @param [Array<Symbol>]
|
73
|
+
# @param [Array<Symbol>] predicates
|
71
74
|
#
|
72
75
|
# @return [MessageSet]
|
73
76
|
#
|
@@ -42,6 +42,10 @@ module Dry
|
|
42
42
|
meta = message.dup
|
43
43
|
text = meta.delete(:text)
|
44
44
|
call(message: text, tokens: tokens, path: path, meta: meta)
|
45
|
+
else
|
46
|
+
raise ArgumentError, <<~STR
|
47
|
+
+message+ must be either a Symbol, String or Hash (#{message.inspect} given)
|
48
|
+
STR
|
45
49
|
end
|
46
50
|
end
|
47
51
|
alias_method :[], :call
|
@@ -5,6 +5,7 @@ require 'dry/equalizer'
|
|
5
5
|
|
6
6
|
require 'dry/validation/constants'
|
7
7
|
require 'dry/validation/message_set'
|
8
|
+
require 'dry/validation/values'
|
8
9
|
|
9
10
|
module Dry
|
10
11
|
module Validation
|
@@ -12,46 +13,67 @@ module Dry
|
|
12
13
|
#
|
13
14
|
# @api public
|
14
15
|
class Result
|
15
|
-
include Dry::Equalizer(:
|
16
|
+
include Dry::Equalizer(:schema_result, :context, :errors, inspect: false)
|
16
17
|
|
17
18
|
# Build a new result
|
18
19
|
#
|
19
|
-
# @param [Dry::Schema::Result]
|
20
|
+
# @param [Dry::Schema::Result] schema_result
|
20
21
|
#
|
21
22
|
# @api private
|
22
|
-
def self.new(
|
23
|
+
def self.new(schema_result, context = ::Concurrent::Map.new, options = EMPTY_HASH)
|
23
24
|
result = super
|
24
25
|
yield(result) if block_given?
|
25
26
|
result.freeze
|
26
27
|
end
|
27
28
|
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
# @!attribute [r] context
|
34
|
-
# @return [Concurrent::Map]
|
35
|
-
# @api public
|
29
|
+
# Context that's shared between rules
|
30
|
+
#
|
31
|
+
# @return [Concurrent::Map]
|
32
|
+
#
|
33
|
+
# @api public
|
36
34
|
attr_reader :context
|
37
35
|
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
36
|
+
# Result from contract's schema
|
37
|
+
#
|
38
|
+
# @return [Dry::Schema::Result]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
attr_reader :schema_result
|
42
|
+
|
43
|
+
# Result options
|
44
|
+
#
|
45
|
+
# @return [Hash]
|
46
|
+
#
|
47
|
+
# @api private
|
41
48
|
attr_reader :options
|
42
49
|
|
43
50
|
# Initialize a new result
|
44
51
|
#
|
45
52
|
# @api private
|
46
|
-
def initialize(
|
47
|
-
@
|
53
|
+
def initialize(schema_result, context, options)
|
54
|
+
@schema_result = schema_result
|
48
55
|
@context = context
|
49
56
|
@options = options
|
50
57
|
@errors = initialize_errors
|
51
58
|
end
|
52
59
|
|
60
|
+
# Return values wrapper with the input processed by schema
|
61
|
+
#
|
62
|
+
# @return [Values]
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
def values
|
66
|
+
@values ||= Values.new(schema_result.to_h)
|
67
|
+
end
|
68
|
+
|
53
69
|
# Get error set
|
54
70
|
#
|
71
|
+
# @!macro errors-options
|
72
|
+
# @param [Hash] new_options
|
73
|
+
# @option new_options [Symbol] :locale Set locale for messages
|
74
|
+
# @option new_options [Boolean] :hints Enable/disable hints
|
75
|
+
# @option new_options [Boolean] :full Get messages that include key names
|
76
|
+
#
|
55
77
|
# @return [MessageSet]
|
56
78
|
#
|
57
79
|
# @api public
|
@@ -81,7 +103,7 @@ module Dry
|
|
81
103
|
#
|
82
104
|
# @api private
|
83
105
|
def error?(key)
|
84
|
-
|
106
|
+
schema_result.error?(key)
|
85
107
|
end
|
86
108
|
|
87
109
|
# Add a new error for the provided key
|
@@ -136,6 +158,7 @@ module Dry
|
|
136
158
|
#
|
137
159
|
# @api private
|
138
160
|
def freeze
|
161
|
+
values.freeze
|
139
162
|
errors.freeze
|
140
163
|
super
|
141
164
|
end
|
@@ -149,7 +172,7 @@ module Dry
|
|
149
172
|
|
150
173
|
# @api private
|
151
174
|
def schema_errors(options)
|
152
|
-
|
175
|
+
schema_result.message_set(options).to_a
|
153
176
|
end
|
154
177
|
end
|
155
178
|
end
|
data/lib/dry/validation/rule.rb
CHANGED
@@ -3,22 +3,34 @@
|
|
3
3
|
require 'dry/equalizer'
|
4
4
|
require 'dry/initializer'
|
5
5
|
|
6
|
+
require 'dry/validation/constants'
|
7
|
+
|
6
8
|
module Dry
|
7
9
|
module Validation
|
8
|
-
# Rules
|
10
|
+
# Rules capture configuration and evaluator blocks
|
11
|
+
#
|
12
|
+
# When a rule is applied, it creates an `Evaluator` using schema result and its
|
13
|
+
# block will be evaluated in the context of the evaluator.
|
14
|
+
#
|
15
|
+
# @see Contract#rule
|
9
16
|
#
|
10
|
-
# @api
|
17
|
+
# @api public
|
11
18
|
class Rule
|
12
19
|
include Dry::Equalizer(:keys, :block, inspect: false)
|
13
20
|
|
14
21
|
extend Dry::Initializer
|
15
22
|
|
16
|
-
# @!
|
23
|
+
# @!attribute [r] keys
|
17
24
|
# @return [Array<Symbol, String, Hash>]
|
18
25
|
# @api private
|
19
26
|
option :keys
|
20
27
|
|
21
|
-
# @!
|
28
|
+
# @!attribute [r] macros
|
29
|
+
# @return [Array<Symbol>]
|
30
|
+
# @api private
|
31
|
+
option :macros, default: proc { EMPTY_ARRAY.dup }
|
32
|
+
|
33
|
+
# @!attribute [r] block
|
22
34
|
# @return [Proc]
|
23
35
|
# @api private
|
24
36
|
option :block
|
@@ -27,11 +39,26 @@ module Dry
|
|
27
39
|
#
|
28
40
|
# @param [Contract] contract
|
29
41
|
# @param [Result] result
|
30
|
-
# @param [Concurrent::Map] context
|
31
42
|
#
|
32
43
|
# @api private
|
33
|
-
def call(contract, result
|
34
|
-
Evaluator.new(
|
44
|
+
def call(contract, result)
|
45
|
+
Evaluator.new(
|
46
|
+
contract,
|
47
|
+
values: result.values, keys: keys, macros: macros, _context: result.context,
|
48
|
+
&block
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Define which macros should be executed
|
53
|
+
#
|
54
|
+
# @see Contract#rule
|
55
|
+
# @return [Rule]
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def validate(*macros, &block)
|
59
|
+
@macros = macros
|
60
|
+
@block = block if block
|
61
|
+
self
|
35
62
|
end
|
36
63
|
|
37
64
|
# Return a nice string representation
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/equalizer'
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Validation
|
7
|
+
# A convenient wrapper for data processed by schemas
|
8
|
+
#
|
9
|
+
# Values are available within the rule blocks. They act as hash-like
|
10
|
+
# objects and expose a convenient API for accessing data.
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
class Values
|
14
|
+
include Enumerable
|
15
|
+
include Dry::Equalizer(:data)
|
16
|
+
|
17
|
+
# Schema's result output
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
attr_reader :data
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def initialize(data)
|
26
|
+
@data = data
|
27
|
+
end
|
28
|
+
|
29
|
+
# Read from the provided key
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# rule(:age) do
|
33
|
+
# key.failure('must be > 18') if values[:age] <= 18
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @param [Symbol] key
|
37
|
+
#
|
38
|
+
# @return [Object]
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def [](key)
|
42
|
+
data[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
def respond_to_missing?(meth, include_private = false)
|
47
|
+
super || data.respond_to?(meth, include_private)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
def method_missing(meth, *args, &block)
|
54
|
+
if data.respond_to?(meth)
|
55
|
+
data.public_send(meth, *args, &block)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-validation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -70,22 +70,16 @@ dependencies:
|
|
70
70
|
name: dry-schema
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0.6'
|
76
73
|
- - "~>"
|
77
74
|
- !ruby/object:Gem::Version
|
78
|
-
version: '0
|
75
|
+
version: '1.0'
|
79
76
|
type: :runtime
|
80
77
|
prerelease: false
|
81
78
|
version_requirements: !ruby/object:Gem::Requirement
|
82
79
|
requirements:
|
83
|
-
- - ">="
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
version: '0.6'
|
86
80
|
- - "~>"
|
87
81
|
- !ruby/object:Gem::Version
|
88
|
-
version: '0
|
82
|
+
version: '1.0'
|
89
83
|
- !ruby/object:Gem::Dependency
|
90
84
|
name: bundler
|
91
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -138,6 +132,7 @@ files:
|
|
138
132
|
- CHANGELOG.md
|
139
133
|
- LICENSE
|
140
134
|
- README.md
|
135
|
+
- config/errors.yml
|
141
136
|
- lib/dry-validation.rb
|
142
137
|
- lib/dry/validation.rb
|
143
138
|
- lib/dry/validation/config.rb
|
@@ -147,11 +142,13 @@ files:
|
|
147
142
|
- lib/dry/validation/evaluator.rb
|
148
143
|
- lib/dry/validation/extensions/hints.rb
|
149
144
|
- lib/dry/validation/extensions/monads.rb
|
145
|
+
- lib/dry/validation/macros.rb
|
150
146
|
- lib/dry/validation/message.rb
|
151
147
|
- lib/dry/validation/message_set.rb
|
152
148
|
- lib/dry/validation/messages/resolver.rb
|
153
149
|
- lib/dry/validation/result.rb
|
154
150
|
- lib/dry/validation/rule.rb
|
151
|
+
- lib/dry/validation/values.rb
|
155
152
|
- lib/dry/validation/version.rb
|
156
153
|
homepage: https://dry-rb.org/gems/dry-validation
|
157
154
|
licenses:
|