dry-validation 1.0.0.rc1 → 1.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97e07b84e6d69b8ff4c887a633dba3862ebb44219d1aa36b0a4394e08761b8e0
4
- data.tar.gz: 18a796e79f03f85f288307038a29c913a0340e6a1c77e2b6b138b96397b0aff9
3
+ metadata.gz: a644e1a1909528377d2edf6acb339bb6e571a9c2959700f9537ed849c9b6c0bc
4
+ data.tar.gz: 22bce26482e71124a4c83c4c9d1a6dd2da2960d02b30080ff672540322247d42
5
5
  SHA512:
6
- metadata.gz: f3f8fd949d909e6823c5e5aced8fb055eec20263f06247b98e066187efc57a1e987b0c016f472b99693a79191aafc64c63b0ec68d64ef8148b2f4901a861335f
7
- data.tar.gz: c146b67aaab383aa39f908b3e487fa249f7eeb9d13b7e2c165a2c845cb5450775c52f3fc3811a900d6a1c2a2e36fc79fc002b2c04a843d0fa1e635888abb7e6a
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
- * [Documentation](http://dry-rb.org/gems/dry-validation)
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
@@ -0,0 +1,4 @@
1
+ en:
2
+ dry_validation:
3
+ errors:
4
+ acceptance: "must accept %{key}"
@@ -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 private
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: -> { :en }
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| result.error?(key) }
101
+ next if rule.keys.any? { |key| error?(result, key) }
92
102
 
93
- rule.(self, result, result.context).failures.each do |failure|
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 [Array<Rule>]
115
+ # @return [Rule]
76
116
  #
77
117
  # @api public
78
118
  def rule(*keys, &block)
79
- rules << Rule.new(keys: keys, block: block)
80
- rules
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(option = nil, &block)
87
- Class.new(self, &block).new(option)
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 private
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 public
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
- instance_exec(_context, &block)
92
+
93
+ instance_exec(_context, &block) if block
94
+
95
+ macros.each do |macro|
96
+ instance_exec(_context, &macro(macro))
97
+ end
88
98
  end
89
99
 
90
- # Get failures object for the default or provided path
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 failures object for base errors
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 for contract results
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(new_opts = EMPTY_HASH)
33
- opts = new_opts.merge(hints: false)
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(new_opts = EMPTY_HASH)
43
- errors.with(hints.to_a, options.merge(**new_opts))
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(new_opts = EMPTY_HASH)
52
- values.hints(new_opts)
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
- # @!attribute [r] text
17
- # @return [String] text The error message text
16
+ # The error message text
17
+ #
18
+ # @return [String] text
19
+ #
20
+ # @api public
18
21
  attr_reader :text
19
22
 
20
- # @!attribute [r] path
21
- # @return [Array<Symbol, Integer>] path The path to the value with the error
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
- # @!attribute [r] meta
25
- # @return [Hash] meta Optional hash with meta-data
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 public
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
- # @!attribute [r] source
15
- # Return the source set of messages used to produce final evaluated messages
16
- # @return [Array<Message, Message::Localized, Schema::Message>]
17
- # @api private
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
- # @!attribute [r] locale
21
- # @return [Symbol] locale
22
- # @api public
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).message_set
70
+ # message_set = contract.(input).errors
68
71
  # message_set.filter(:base?)
69
72
  #
70
- # @param [Array<Symbol>] *predicates
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(:values, :context, :errors)
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(values, context = ::Concurrent::Map.new, options = EMPTY_HASH)
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
- # @!attribute [r] values
29
- # @return [Dry::Schema::Result]
30
- # @api private
31
- attr_reader :values
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
- # @!attribute [r] options
39
- # @return [Hash]
40
- # @api private
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(values, context, options)
47
- @values = values
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
- values.error?(key)
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
- values.message_set(options).to_a
175
+ schema_result.message_set(options).to_a
153
176
  end
154
177
  end
155
178
  end
@@ -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 are created by contracts
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 private
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
- # @!atrribute [r] keys
23
+ # @!attribute [r] keys
17
24
  # @return [Array<Symbol, String, Hash>]
18
25
  # @api private
19
26
  option :keys
20
27
 
21
- # @!atrribute [r] block
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, context)
34
- Evaluator.new(contract, values: result, keys: keys, _context: context, &block)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Validation
5
- VERSION = '1.0.0.rc1'
5
+ VERSION = '1.0.0.rc3'
6
6
  end
7
7
  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.rc1
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-04-26 00:00:00.000000000 Z
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.6'
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.6'
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: