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 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: