dry-validation 1.0.0.alpha2 → 1.0.0.beta1

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: 288a3f0d9af21271cfeb794d48752ba6bfa93c8e4fa088e8faa9f6eb9a05e083
4
- data.tar.gz: 1949525da40fb528c5eda20c610a5c017c260600613ba4fc392f23cd6a84a8d7
3
+ metadata.gz: f6a47d27d49e9894cf283af7e4809cea07f45e5f8f3f52a180dd20d8c849b315
4
+ data.tar.gz: 83f1e9b5ff94c4db24769624dd1552f0876be541926ffd57d43c2490757ab662
5
5
  SHA512:
6
- metadata.gz: f4884bb2c55f0b987b0aad70bceaedc0d1bc5adc5056bd47e9606e126a9ce9bcb450f5ec7c2035b35572eb4677c4ee0c1525856fe83866b07bf559a75b97a260
7
- data.tar.gz: f8f79772a1950752dfa03204269df85d7480c52114c4369d2e895e1edd02b6f02ee3bc03049a2c4ed0994042b104b82dd0e22fcb1c31882b7b0c741d414c8ae1
6
+ metadata.gz: 67ab872380ecdc45fbe1efeb8cec95a03dff692aae2e74ca498483ceb7104b6a2d5cd78bdc23884d69d89665f110f79c3fa67285b97a40cc2c3625f9744bc34f
7
+ data.tar.gz: f306b9dc73085526c485a7b18f2e33c83d08dd72922f1640bbc9b3b6ea0164ad1fbda752da0bb9b5d1f55dbd47c6ca6aa9ebef297035fd7adb22bb18be879c4e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ # v1.0.0.beta1 2019-03-26
2
+
3
+ ### Added
4
+
5
+ * New API for setting failures `base.failure` for base errors and `key.failure` for key errors (solnic)
6
+ * Support for `base` errors associated with a key even when child keys have errors too (solnic)
7
+ * Support for `base` errors not associated with any key (solnic)
8
+ * Result objects use `ErrorSet` object now for managing messages (solnic)
9
+ * Nested keys are properly handled when generating messages hash (issue #489) (flash-gordon + solnic)
10
+ * Result objects support `locale` and `full` options now (solnic)
11
+ * Ability to configure `top_namespace` for messages, which will be used for both schema and rule localization (solnic)
12
+ * Rule blocks receive a context object that you can use to share data between rules (solnic)
13
+
14
+ ### Changed
15
+
16
+ * [BREAKING] `Result#errors` returns an instance of `ErrorSet` now, it's an enumerable, coerible to a hash (solnic)
17
+ * [BREAKING] `failure` was removed in favor of `key.failure` or `key(:foo).failure` (solnic)
18
+ * [BREAKING] `Result#to_hash` was removed (flash-gordon)
19
+
20
+ [Compare v1.0.0.alpha2...v1.0.0.beta1](https://github.com/dry-rb/dry-validation/compare/v1.0.0.alpha2...v1.0.0.beta1)
21
+
1
22
  # v1.0.0.alpha2 2019-03-05
2
23
 
3
24
  First round of bug fixes. Thanks for testing <3!
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/schema/config'
4
+
5
+ module Dry
6
+ module Validation
7
+ class Config < Schema::Config
8
+ end
9
+ end
10
+ end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/schema'
4
- require 'dry/validation/messages'
4
+ require 'dry/schema/messages'
5
+
5
6
  require 'dry/validation/constants'
6
7
 
7
8
  module Dry
@@ -11,6 +12,21 @@ module Dry
11
12
  #
12
13
  # @api public
13
14
  module ClassInterface
15
+ # @api private
16
+ def inherited(klass)
17
+ super
18
+ klass.instance_variable_set('@config', config.dup)
19
+ end
20
+
21
+ # Configuration
22
+ #
23
+ # @return [Config]
24
+ #
25
+ # @api public
26
+ def config
27
+ @config ||= Validation::Config.new
28
+ end
29
+
14
30
  # Define a params schema for your contract
15
31
  #
16
32
  # This type of schema is suitable for HTTP parameters
@@ -78,14 +94,14 @@ module Dry
78
94
 
79
95
  # @api private
80
96
  def rules
81
- @__rules__ ||= EMPTY_ARRAY
82
- .dup
83
- .concat(superclass.respond_to?(:rules) ? superclass.rules : EMPTY_ARRAY)
97
+ @rules ||= EMPTY_ARRAY
98
+ .dup
99
+ .concat(superclass.respond_to?(:rules) ? superclass.rules : EMPTY_ARRAY)
84
100
  end
85
101
 
86
102
  # @api private
87
103
  def messages
88
- @__messages__ ||= Messages.setup(config)
104
+ @messages ||= Schema::Messages.setup(config.messages)
89
105
  end
90
106
 
91
107
  private
@@ -97,7 +113,9 @@ module Dry
97
113
 
98
114
  # @api private
99
115
  def define(method_name, &block)
100
- raise ::Dry::Validation::DuplicateSchemaError, 'Schema has already been defined' if defined?(@__schema__)
116
+ if defined?(@__schema__)
117
+ raise ::Dry::Validation::DuplicateSchemaError, 'Schema has already been defined'
118
+ end
101
119
 
102
120
  case method_name
103
121
  when :schema
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'concurrent/map'
4
+
3
5
  require 'dry/equalizer'
4
- require 'dry/configurable'
5
6
  require 'dry/initializer'
6
7
 
8
+ require 'dry/validation/config'
7
9
  require 'dry/validation/constants'
8
10
  require 'dry/validation/rule'
9
11
  require 'dry/validation/evaluator'
12
+ require 'dry/validation/messages/resolver'
10
13
  require 'dry/validation/result'
14
+ require 'dry/validation/error'
11
15
  require 'dry/validation/contract/class_interface'
12
16
 
13
17
  module Dry
@@ -47,40 +51,20 @@ module Dry
47
51
  class Contract
48
52
  include Dry::Equalizer(:schema, :rules, :messages)
49
53
 
50
- extend Dry::Configurable
51
54
  extend Dry::Initializer
52
55
  extend ClassInterface
53
56
 
54
- # @!group Configuration
55
-
56
- # @overload config.messages=(identifier)
57
- # Set message backend
58
- #
59
- # @param identifier [Symbol] the backend identifier, either `:yaml` or `:i18n`
60
- #
61
- # @api public
62
- # @!scope class
63
- setting :messages, :yaml
57
+ config.messages.top_namespace = 'dry_validation'
64
58
 
65
- # @overload config.messages_file=(path)
66
- # Set additional path to messages file
67
- #
68
- # @param path [String, Pathname] the path
69
- #
59
+ # @!attribute [r] config
60
+ # @return [Config]
70
61
  # @api public
71
- # @!scope class
72
- setting :messages_file
62
+ option :config, default: -> { self.class.config }
73
63
 
74
- # @overload config.namespace=(name)
75
- # Set namespace that will be used to override default messages
76
- #
77
- # @param name [Symbol] the namespace
78
- #
64
+ # @!attribute [r] locale
65
+ # @return [Symbol]
79
66
  # @api public
80
- # @!scope class
81
- setting :namespace
82
-
83
- # @!endgroup
67
+ option :locale, default: -> { :en }
84
68
 
85
69
  # @!attribute [r] schema
86
70
  # @return [Dry::Schema::Params, Dry::Schema::JSON, Dry::Schema::Processor]
@@ -92,10 +76,10 @@ module Dry
92
76
  # @api private
93
77
  option :rules, default: -> { self.class.rules }
94
78
 
95
- # @!attribute [r] messages
96
- # @return [Messages::I18n, Messages::YAML]
79
+ # @!attribute [r] message_resolver
80
+ # @return [Messages::Resolver]
97
81
  # @api private
98
- option :messages, default: -> { self.class.messages }
82
+ option :message_resolver, default: -> { Messages::Resolver.new(self.class.messages, locale) }
99
83
 
100
84
  # Apply contract to an input
101
85
  #
@@ -103,36 +87,18 @@ module Dry
103
87
  #
104
88
  # @api public
105
89
  def call(input)
106
- Result.new(schema.(input)) do |result|
90
+ Result.new(schema.(input), locale: locale) do |result|
91
+ context = Concurrent::Map.new
92
+
107
93
  rules.each do |rule|
108
94
  next if rule.keys.any? { |key| result.error?(key) }
109
95
 
110
- rule_result = rule.(self, result)
111
- result.add_error(*rule_result.message) if rule_result.failure?
96
+ rule.(self, result, context).failures.each do |failure|
97
+ result.add_error(message_resolver[failure])
98
+ end
112
99
  end
113
100
  end
114
101
  end
115
-
116
- # Get message text for the given rule name and options
117
- #
118
- # @return [String]
119
- #
120
- # @api private
121
- def message(key, tokens: EMPTY_HASH, **opts)
122
- msg_opts = opts.merge(tokens)
123
- rule_path = Array(opts.fetch(:rule)).flatten
124
-
125
- template = messages[key, msg_opts.merge(rule: rule_path.join(DOT))]
126
- template ||= messages[key, msg_opts.merge(rule: rule_path.last)]
127
-
128
- unless template
129
- raise MissingMessageError, <<~STR
130
- Message template for #{key.inspect} under #{rule_path.join(DOT).inspect} was not found
131
- STR
132
- end
133
-
134
- template.(template.data(tokens))
135
- end
136
102
  end
137
103
  end
138
104
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/equalizer'
4
+ require 'dry/schema/message'
5
+
6
+ module Dry
7
+ module Validation
8
+ # Error message
9
+ #
10
+ # @api public
11
+ class Error < Schema::Message
12
+ include Dry::Equalizer(:text, :path)
13
+
14
+ # @!attribute [r] text
15
+ # @return [String] text The error message text
16
+ attr_reader :text
17
+
18
+ # @!attribute [r] path
19
+ # @return [Array<Symbol, Integer>] path The path to the value with the error
20
+ attr_reader :path
21
+
22
+ # @api public
23
+ class Localized < Error
24
+ # @api public
25
+ def evaluate(**opts)
26
+ Error.new(text.(opts), path: path)
27
+ end
28
+ end
29
+
30
+ # Build an error
31
+ #
32
+ # @return [Error, Error::Localized]
33
+ #
34
+ # @api public
35
+ def self.[](text, path)
36
+ text.respond_to?(:call) ? Localized.new(text, path: path) : Error.new(text, path: path)
37
+ end
38
+
39
+ # Initialize a new error object
40
+ #
41
+ # @api private
42
+ def initialize(text, path:)
43
+ @text = text
44
+ @path = Array(path)
45
+ end
46
+
47
+ # Check if this is a base error not associated with any key
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ # @api public
52
+ def base?
53
+ @base ||= path.compact.empty?
54
+ end
55
+
56
+ # Dump error to a string
57
+ #
58
+ # @return [String]
59
+ #
60
+ # @api public
61
+ def to_s
62
+ text
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/schema/message_set'
4
+
5
+ require 'dry/validation/constants'
6
+ require 'dry/validation/error'
7
+
8
+ module Dry
9
+ module Validation
10
+ # ErrorSet is a specialized message set for handling validation errors
11
+ #
12
+ # @api public
13
+ class ErrorSet < Schema::MessageSet
14
+ # @!attribute [r] source
15
+ # Return the source set of messages used to produce final evaluated messages
16
+ # @return [Array<Error, Error::Localized, Schema::Message>]
17
+ # @api private
18
+ attr_reader :source_messages
19
+
20
+ # @!attribute [r] locale
21
+ # @return [Symbol] locale
22
+ # @api public
23
+ attr_reader :locale
24
+
25
+ # @api private
26
+ def initialize(messages, options = EMPTY_HASH)
27
+ @locale = options.fetch(:locale, :en)
28
+ @source_messages = options.fetch(:source, messages.dup)
29
+ super
30
+ end
31
+
32
+ # Return a new error set using updated options
33
+ #
34
+ # @return [ErrorSet]
35
+ #
36
+ # @api private
37
+ def with(other, new_options = EMPTY_HASH)
38
+ return self if new_options.empty?
39
+
40
+ self.class.new(
41
+ other + select { |err| err.is_a?(Error) },
42
+ options.merge(source: source_messages, **new_options)
43
+ ).freeze
44
+ end
45
+
46
+ # Add a new error
47
+ #
48
+ # This is used when result is being prepared
49
+ #
50
+ # @return [ErrorSet]
51
+ #
52
+ # @api private
53
+ def add(error)
54
+ source_messages << error
55
+ messages << error
56
+ initialize_placeholders!
57
+ self
58
+ end
59
+
60
+ # Filter error set using provided predicates
61
+ #
62
+ # This method is open to any predicate because errors can be anything that
63
+ # implements Message API, thus they can implement whatever predicates you
64
+ # may need.
65
+ #
66
+ # @example get a list of base errors
67
+ # error_set = contract.(input).error_set
68
+ # error_set.filter(:base?)
69
+ #
70
+ # @param [Array<Symbol>] *predicates
71
+ #
72
+ # @return [ErrorSet]
73
+ #
74
+ # @api public
75
+ def filter(*predicates)
76
+ errors = select { |e|
77
+ predicates.all? { |predicate| e.respond_to?(predicate) && e.public_send(predicate) }
78
+ }
79
+ self.class.new(errors)
80
+ end
81
+
82
+ # @api private
83
+ def freeze
84
+ source_messages.select { |err| err.respond_to?(:evaluate) }.each do |err|
85
+ idx = source_messages.index(err)
86
+ msg = err.evaluate(locale: locale, full: options[:full])
87
+ messages[idx] = msg
88
+ end
89
+ to_h
90
+ self
91
+ end
92
+
93
+ private
94
+
95
+ # @api private
96
+ def unique_paths
97
+ source_messages.uniq(&:path).map(&:path)
98
+ end
99
+
100
+ # @api private
101
+ def messages_map
102
+ @messages_map ||= reduce(placeholders) { |hash, msg|
103
+ node = msg.path.reduce(hash) { |a, e| a.is_a?(Hash) ? a[e] : a.last[e] }
104
+ (node.size > 1 ? node[0] : node) << msg.to_s
105
+ hash
106
+ }
107
+ end
108
+
109
+ # @api private
110
+ #
111
+ # rubocop:disable Metrics/AbcSize
112
+ # rubocop:disable Metrics/PerceivedComplexity
113
+ def initialize_placeholders!
114
+ @placeholders = unique_paths.each_with_object(EMPTY_HASH.dup) { |path, hash|
115
+ curr_idx = 0
116
+ last_idx = path.size - 1
117
+ node = hash
118
+
119
+ while curr_idx <= last_idx
120
+ key = path[curr_idx]
121
+
122
+ next_node =
123
+ if node.is_a?(Array) && key.is_a?(Symbol)
124
+ node_hash = (node << [] << {}).last
125
+ node_hash[key] || (node_hash[key] = curr_idx < last_idx ? {} : [])
126
+ else
127
+ node[key] || (node[key] = curr_idx < last_idx ? {} : [])
128
+ end
129
+
130
+ node = next_node
131
+ curr_idx += 1
132
+ end
133
+ }
134
+ end
135
+ # rubocop:enable Metrics/AbcSize
136
+ # rubocop:enable Metrics/PerceivedComplexity
137
+ end
138
+ end
139
+ end
@@ -15,103 +15,128 @@ module Dry
15
15
  class Evaluator
16
16
  extend Dry::Initializer
17
17
 
18
- # @!attribute [r] _context
18
+ ROOT_PATH = [nil].freeze
19
+
20
+ # Failure accumulator object
21
+ #
22
+ # @api public
23
+ class Failures
24
+ # @api private
25
+ attr_reader :path
26
+
27
+ # @api private
28
+ attr_reader :opts
29
+
30
+ # @api private
31
+ def initialize(path = ROOT_PATH)
32
+ @path = Dry::Schema::Path[path]
33
+ @opts = []
34
+ end
35
+
36
+ # Set failure
37
+ #
38
+ # @overload failure(message)
39
+ # Set message text explicitly
40
+ # @param message [String] The message text
41
+ # @example
42
+ # failure('this failed')
43
+ #
44
+ # @overload failure(id)
45
+ # Use message identifier (needs localized messages setup)
46
+ # @param id [Symbol] The message id
47
+ # @example
48
+ # failure(:taken)
49
+ #
50
+ # @api public
51
+ def failure(message, tokens = EMPTY_HASH)
52
+ @opts << { message: message, tokens: tokens, path: path }
53
+ self
54
+ end
55
+ end
56
+
57
+ # @!attribute [r] _contract
19
58
  # @return [Contract]
20
59
  # @api private
21
- param :_context
60
+ param :_contract
22
61
 
23
62
  # @!attribute [r] keys
24
63
  # @return [Array<String, Symbol, Hash>]
25
64
  # @api private
26
65
  option :keys
27
66
 
28
- # @!attribute [r] default_id
29
- # @return [String, Symbol, Hash]
67
+ # @!attribute [r] _context
68
+ # @return [Concurrent::Map]
69
+ # @api public
70
+ option :_context
71
+
72
+ # @!attribute [r] path
73
+ # @return [Dry::Schema::Path]
30
74
  # @api private
31
- option :default_id, default: proc { keys.first }
75
+ option :path, default: proc { Dry::Schema::Path[(key = keys.first) ? key : ROOT_PATH] }
32
76
 
33
77
  # @!attribute [r] values
34
78
  # @return [Object]
35
79
  # @api private
36
80
  option :values
37
81
 
38
- # @!attribute [r] message
39
- # @return [String]
40
- # @api private
41
- attr_reader :message
42
-
43
82
  # Initialize a new evaluator
44
83
  #
45
84
  # @api private
46
85
  def initialize(*args, &block)
47
86
  super(*args)
48
- @failure = false
49
- instance_eval(&block)
87
+ instance_exec(_context, &block)
50
88
  end
51
89
 
52
- # Set failure message
90
+ # Get failures object for the default or provided path
53
91
  #
54
- # @overload failure(message)
55
- # Set message text explicitly
56
- # @param message [String] The message text
57
- # @example
58
- # failure('this failed')
92
+ # @param [Symbol,String,Hash,Array<Symbol>] path
59
93
  #
60
- # @overload failure(id)
61
- # Use message identifier (needs localized messages setup)
62
- # @param id [Symbol] The message id
63
- # @example
64
- # failure(:taken)
94
+ # @return [Failures]
95
+ #
96
+ # @see Failures#failure
97
+ #
98
+ # @api public
99
+ def key(path = self.path)
100
+ (@key ||= EMPTY_HASH.dup)[path] ||= Failures.new(path)
101
+ end
102
+
103
+ # Get failures object for base errors
65
104
  #
66
- # @overload failure(key, message)
67
- # Set message under specified key (overrides rule's default key)
68
- # @param id [Symbol] The message key
69
- # @example
70
- # failure(:my_error, 'this failed')
105
+ # @return [Failures]
71
106
  #
72
- # @return [Evaluator]
107
+ # @see Failures#failure
73
108
  #
74
109
  # @api public
75
- def failure(*args, **tokens)
76
- id, text =
77
- if args.size.equal?(1)
78
- case (msg = args[0])
79
- when Symbol
80
- [default_id, _context.message(msg, rule: default_id, tokens: tokens)]
81
- when String
82
- [default_id, msg]
83
- end
84
- else
85
- args
86
- end
87
- @failure = true
88
- @message = [id, text]
89
- self
110
+ def base
111
+ @base ||= Failures.new
90
112
  end
91
113
 
92
- # Check if evaluation resulted in a failure message
114
+ # Return aggregated failures
93
115
  #
94
- # @return [Boolean]
116
+ # @return [Array<Hash>]
95
117
  #
96
118
  # @api private
97
- def failure?
98
- @failure.equal?(true)
119
+ def failures
120
+ failures = []
121
+ failures += @base.opts if defined?(@base)
122
+ failures.concat(@key.values.flat_map(&:opts)) if defined?(@key)
123
+ failures
99
124
  end
100
125
 
101
126
  # @api private
102
127
  def respond_to_missing?(meth, include_private = false)
103
- super || _context.respond_to?(meth, true)
128
+ super || _contract.respond_to?(meth, true)
104
129
  end
105
130
 
106
131
  private
107
132
 
108
- # Forward to the underlying context
133
+ # Forward to the underlying contract
109
134
  #
110
135
  # @api private
111
136
  def method_missing(meth, *args, &block)
112
137
  # yes, we do want to delegate to private methods too
113
- if _context.respond_to?(meth, true)
114
- _context.__send__(meth, *args, &block)
138
+ if _contract.respond_to?(meth, true)
139
+ _contract.__send__(meth, *args, &block)
115
140
  else
116
141
  super
117
142
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Validation
5
+ module Messages
6
+ # Resolve translated messages from failure arguments
7
+ #
8
+ # @api public
9
+ class Resolver
10
+ # @!attribute [r] messages
11
+ # @return [Messages::I18n, Messages::YAML] messages backend
12
+ # @api private
13
+ attr_reader :messages
14
+
15
+ # @!attribute [r] locale
16
+ # @return [Symbol] current locale
17
+ # @api private
18
+ attr_reader :locale
19
+
20
+ # @api private
21
+ def initialize(messages, locale = :en)
22
+ @messages = messages
23
+ @locale = locale
24
+ end
25
+
26
+ # Resolve Error object from provided args and path
27
+ #
28
+ # This is used internally by contracts when rules are applied
29
+ #
30
+ # @return [Error, Error::Localized]
31
+ #
32
+ # @api public
33
+ def call(message:, tokens:, path:)
34
+ case message
35
+ when Symbol
36
+ Error[->(**opts) { message(message, path: path, tokens: tokens, **opts) }, path]
37
+ when String
38
+ Error[message, path]
39
+ end
40
+ end
41
+ alias_method :[], :call
42
+
43
+ # Resolve a message
44
+ #
45
+ # @return [String]
46
+ #
47
+ # @api public
48
+ def message(rule, tokens: EMPTY_HASH, path:, locale: self.locale, full: false)
49
+ keys = path.to_a.compact
50
+ msg_opts = tokens.merge(path: keys, locale: locale)
51
+
52
+ if keys.empty?
53
+ template = messages["rules.#{rule}", msg_opts]
54
+ else
55
+ template = messages[rule, msg_opts.merge(path: keys.join(DOT))]
56
+ template ||= messages[rule, msg_opts.merge(path: keys.last)]
57
+ end
58
+
59
+ unless template
60
+ raise MissingMessageError, <<~STR
61
+ Message template for #{rule.inspect} under #{keys.join(DOT).inspect} was not found
62
+ STR
63
+ end
64
+
65
+ text = template.(template.data(tokens))
66
+
67
+ full ? "#{messages.rule(keys.last, msg_opts)} #{text}" : text
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/equalizer'
4
+
4
5
  require 'dry/validation/constants'
6
+ require 'dry/validation/error_set'
5
7
 
6
8
  module Dry
7
9
  module Validation
@@ -13,8 +15,10 @@ module Dry
13
15
 
14
16
  # Build a new result
15
17
  #
18
+ # @param [Dry::Schema::Result]
19
+ #
16
20
  # @api private
17
- def self.new(params, errors = EMPTY_HASH.dup)
21
+ def self.new(values, options = EMPTY_HASH)
18
22
  result = super
19
23
  yield(result) if block_given?
20
24
  result.freeze
@@ -25,25 +29,29 @@ module Dry
25
29
  # @api private
26
30
  attr_reader :values
27
31
 
28
- # @!attribute [r] errors
29
- # @return [Hash<Symbol=>Array<String>>]
30
- # @api public
31
- attr_reader :errors
32
+ # @!attribute [r] options
33
+ # @return [Hash]
34
+ # @api private
35
+ attr_reader :options
32
36
 
33
37
  # Initialize a new result
34
38
  #
35
39
  # @api private
36
- def initialize(values, errors)
40
+ def initialize(values, options)
37
41
  @values = values
38
- @errors = errors.update(values.errors)
42
+ @options = options
43
+ @errors = initialize_errors
39
44
  end
40
45
 
41
- # Return all messages including hints from schema
46
+ # Get error set
47
+ #
48
+ # @return [ErrorSet]
42
49
  #
43
50
  # @api public
44
- def messages
45
- values.messages
51
+ def errors(new_options = EMPTY_HASH)
52
+ new_options.empty? ? @errors : @errors.with(schema_errors(new_options), new_options)
46
53
  end
54
+ alias_method :messages, :errors
47
55
 
48
56
  # Check if result is successful
49
57
  #
@@ -51,7 +59,7 @@ module Dry
51
59
  #
52
60
  # @api public
53
61
  def success?
54
- errors.empty?
62
+ @errors.empty?
55
63
  end
56
64
 
57
65
  # Check if result is not successful
@@ -73,8 +81,8 @@ module Dry
73
81
  # Add a new error for the provided key
74
82
  #
75
83
  # @api private
76
- def add_error(key, message)
77
- add_to(errors, key, message)
84
+ def add_error(error)
85
+ errors.add(error)
78
86
  self
79
87
  end
80
88
 
@@ -86,27 +94,7 @@ module Dry
86
94
  #
87
95
  # @api public
88
96
  def [](key)
89
- if values.key?(key)
90
- values[key]
91
- elsif storage.key?(key)
92
- storage[key]
93
- else
94
- nil
95
- end
96
- end
97
-
98
- # Store value under specified key
99
- #
100
- # @param [Symbol] key
101
- # @param [Object] value
102
- #
103
- # @return [Object]
104
- #
105
- # @api public
106
- def []=(key, value)
107
- raise ArgumentError, "Key +#{key}+ was already set" if key?(key)
108
-
109
- storage[key] = value
97
+ values[key]
110
98
  end
111
99
 
112
100
  # Check if a key was set
@@ -117,7 +105,7 @@ module Dry
117
105
  #
118
106
  # @api public
119
107
  def key?(key)
120
- values.key?(key) || storage.key?(key)
108
+ values.key?(key)
121
109
  end
122
110
 
123
111
  # Coerce to a hash
@@ -126,38 +114,32 @@ module Dry
126
114
  def to_h
127
115
  values.to_h
128
116
  end
129
- alias_method :to_hash, :to_h
130
-
131
- # Add new errors
132
- #
133
- # @api private
134
- def update(new_errors)
135
- errors.update(new_errors)
136
- end
137
117
 
138
118
  # Return a string representation
139
119
  #
140
120
  # @api public
141
121
  def inspect
142
- "#<#{self.class}#{to_h.inspect} errors=#{errors.inspect}>"
122
+ "#<#{self.class}#{to_h.inspect} errors=#{errors.to_h.inspect}>"
123
+ end
124
+
125
+ # Freeze result and its error set
126
+ #
127
+ # @api private
128
+ def freeze
129
+ errors.freeze
130
+ super
143
131
  end
144
132
 
145
133
  private
146
134
 
147
135
  # @api private
148
- def storage
149
- @storage ||= EMPTY_HASH.dup
136
+ def initialize_errors(options = self.options)
137
+ ErrorSet.new(schema_errors(options), options)
150
138
  end
151
139
 
152
140
  # @api private
153
- def add_to(hash, path, value)
154
- if path.is_a?(Hash)
155
- key = path.keys[0]
156
- messages = (hash[key] ||= EMPTY_HASH.dup)
157
- add_to(messages, path[key], value)
158
- else
159
- (hash[path] ||= EMPTY_ARRAY.dup) << value
160
- end
141
+ def schema_errors(options)
142
+ values.message_set(options).to_a
161
143
  end
162
144
  end
163
145
  end
@@ -25,12 +25,13 @@ module Dry
25
25
 
26
26
  # Evaluate the rule within the provided context
27
27
  #
28
- # @param [Contract] context
29
- # @param [Object] values
28
+ # @param [Contract] contract
29
+ # @param [Result] result
30
+ # @param [Concurrent::Map] context
30
31
  #
31
32
  # @api private
32
- def call(context, values)
33
- Evaluator.new(context, values: values, keys: keys, &block)
33
+ def call(contract, result, context)
34
+ Evaluator.new(contract, values: result, keys: keys, _context: context, &block)
34
35
  end
35
36
  end
36
37
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Validation
5
- VERSION = '1.0.0.alpha2'
5
+ VERSION = '1.0.0.beta1'
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.alpha2
4
+ version: 1.0.0.beta1
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-03-05 00:00:00.000000000 Z
11
+ date: 2019-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -24,26 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
- - !ruby/object:Gem::Dependency
28
- name: dry-configurable
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.1'
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: 0.1.3
37
- type: :runtime
38
- prerelease: false
39
- version_requirements: !ruby/object:Gem::Requirement
40
- requirements:
41
- - - "~>"
42
- - !ruby/object:Gem::Version
43
- version: '0.1'
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: 0.1.3
47
27
  - !ruby/object:Gem::Dependency
48
28
  name: dry-core
49
29
  requirement: !ruby/object:Gem::Requirement
@@ -96,22 +76,22 @@ dependencies:
96
76
  name: dry-schema
97
77
  requirement: !ruby/object:Gem::Requirement
98
78
  requirements:
99
- - - ">="
100
- - !ruby/object:Gem::Version
101
- version: '0.3'
102
79
  - - "~>"
103
80
  - !ruby/object:Gem::Version
104
81
  version: '0.3'
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0.4'
105
85
  type: :runtime
106
86
  prerelease: false
107
87
  version_requirements: !ruby/object:Gem::Requirement
108
88
  requirements:
109
- - - ">="
110
- - !ruby/object:Gem::Version
111
- version: '0.3'
112
89
  - - "~>"
113
90
  - !ruby/object:Gem::Version
114
91
  version: '0.3'
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0.4'
115
95
  - !ruby/object:Gem::Dependency
116
96
  name: bundler
117
97
  requirement: !ruby/object:Gem::Requirement
@@ -166,12 +146,15 @@ files:
166
146
  - README.md
167
147
  - lib/dry-validation.rb
168
148
  - lib/dry/validation.rb
149
+ - lib/dry/validation/config.rb
169
150
  - lib/dry/validation/constants.rb
170
151
  - lib/dry/validation/contract.rb
171
152
  - lib/dry/validation/contract/class_interface.rb
153
+ - lib/dry/validation/error.rb
154
+ - lib/dry/validation/error_set.rb
172
155
  - lib/dry/validation/evaluator.rb
173
156
  - lib/dry/validation/extensions/monads.rb
174
- - lib/dry/validation/messages.rb
157
+ - lib/dry/validation/messages/resolver.rb
175
158
  - lib/dry/validation/result.rb
176
159
  - lib/dry/validation/rule.rb
177
160
  - lib/dry/validation/version.rb
@@ -194,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
177
  - !ruby/object:Gem::Version
195
178
  version: 1.3.1
196
179
  requirements: []
197
- rubygems_version: 3.0.1
180
+ rubygems_version: 3.0.3
198
181
  signing_key:
199
182
  specification_version: 4
200
183
  summary: Validation library
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry/schema/messages'
4
-
5
- module Dry
6
- module Validation
7
- module Messages
8
- class YAML < Schema::Messages::YAML
9
- config.root = config.root.gsub('dry_schema', 'dry_validation')
10
- config.rule_lookup_paths = config.rule_lookup_paths.map { |path|
11
- path.gsub('dry_schema', 'dry_validation')
12
- }
13
- end
14
-
15
- if defined?(::I18n)
16
- class I18n < Schema::Messages::I18n
17
- config.root = config.root.gsub('dry_schema', 'dry_validation')
18
- config.rule_lookup_paths = config.rule_lookup_paths.map { |path|
19
- path.gsub('dry_schema', 'dry_validation')
20
- }
21
- end
22
- end
23
-
24
- # @api private
25
- def self.setup(config)
26
- messages = build(config)
27
-
28
- if config.messages_file && config.namespace
29
- messages.merge(config.messages_file).namespaced(config.namespace)
30
- elsif config.messages_file
31
- messages.merge(config.messages_file)
32
- elsif config.namespace
33
- messages.namespaced(config.namespace)
34
- else
35
- messages
36
- end
37
- end
38
-
39
- # @api private
40
- def self.build(config)
41
- klass =
42
- case config.messages
43
- when :yaml then default
44
- when :i18n then Messages::I18n
45
- else
46
- raise "+#{config.messages}+ is not a valid messages identifier"
47
- end
48
-
49
- klass.build
50
- end
51
-
52
- # @api private
53
- def self.default
54
- Messages::YAML
55
- end
56
- end
57
- end
58
- end