diff --git a/docsite/source/rules.html.md b/docsite/source/rules.html.md index 5ff6125e..137458a0 100644 --- a/docsite/source/rules.html.md +++ b/docsite/source/rules.html.md @@ -181,6 +181,46 @@ contract.call(email: 'jane@doe.org', login: 'jane', password: "").errors.to_h # => {:password=>["password is required"]} ``` +### Checking for previous errors + +Sometimes you may be interested in adding an error when some other error has happened. + +With `#schema_error?(key)` you can check whether the schema has an error for a given key: + +```ruby +class PersonContract < Dry::Validation::Contract + schema do + required(:email).filled(:string) + required(:name).filled(:string) + end + + rule(:name) do + key.failure('first introduce a valid email') if schema_error?(:email) + end +end + +PersonContract.new.(email: nil, name: 'foo').errors.to_h +# { email: ['must be a string'], name: ['first introduce a valid email'] } +``` + +In complex rules you may be interested to know whether the current rule already had an error. For that, you can use `#rule_error?` + +```ruby +class FooContract < Dry::Validation::Contract + schema do + required(:foo).filled(:string) + end + + rule(:foo) do + key.failure('failure added') + key.failure('failure added after checking') if rule_error? + end +end + +FooContract.new.(foo: 'foo').errors.to_h +# { foo: ['failure added', 'failure added after checking'] } +``` + ### Defining a rule for each element of an array To check each element of an array you can simply use `Rule#each` shortcut. It works just like a normal rule, which means it's only applied when a value passed schema checks and supports setting failure messages in the standard way. diff --git a/lib/dry/validation/evaluator.rb b/lib/dry/validation/evaluator.rb index 2ff46738..d68a3026 100644 --- a/lib/dry/validation/evaluator.rb +++ b/lib/dry/validation/evaluator.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'dry/initializer' +require 'dry/core/deprecations' require 'dry/validation/constants' require 'dry/validation/failures' @@ -16,6 +17,9 @@ module Validation # @api public class Evaluator extend Dry::Initializer + extend Dry::Core::Deprecations[:'dry-validation'] + + deprecate :error?, :schema_error? # @!attribute [r] _contract # @return [Contract] @@ -163,17 +167,26 @@ def key? values.key?(key_name) end - # Check if there are any errors under the provided path + # Check if there are any errors on the schema under the provided path # # @param [Symbol, String, Array] A Path-compatible spec # # @return [Boolean] # # @api public - def error?(path) + def schema_error?(path) result.error?(path) end + # Check if there are any errors on the current rule + # + # @return [Boolean] + # + # @api public + def rule_error? + !key(path).empty? + end + # @api private def respond_to_missing?(meth, include_private = false) super || _contract.respond_to?(meth, true) diff --git a/lib/dry/validation/failures.rb b/lib/dry/validation/failures.rb index 50479ff9..a3a0003c 100644 --- a/lib/dry/validation/failures.rb +++ b/lib/dry/validation/failures.rb @@ -60,6 +60,11 @@ def failure(message, tokens = EMPTY_HASH) opts << { message: message, tokens: tokens, path: path } self end + + # @api private + def empty? + opts.empty? + end end end end diff --git a/spec/integration/contract/evaluator/errors_spec.rb b/spec/integration/contract/evaluator/errors_spec.rb new file mode 100644 index 00000000..fa4171fe --- /dev/null +++ b/spec/integration/contract/evaluator/errors_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +RSpec.describe Dry::Validation::Evaluator do + describe '#schema_error?' do + let(:contract) do + Class.new(Dry::Validation::Contract) do + schema do + required(:email).filled(:string) + required(:name).filled(:string) + end + + rule(:name) do + key.failure('first introduce a valid email') if schema_error?(:email) + end + end + end + + it 'checks for errors in given key' do + expect(contract.new.(email: nil, name: 'foo').errors.to_h).to eql( + email: ['must be a string'], + name: ['first introduce a valid email'] + ) + end + end + + describe '#rule_error?' do + let(:contract) do + Class.new(Dry::Validation::Contract) do + schema do + required(:foo).filled(:string) + end + + rule(:foo) do + key.failure('failure added') + key.failure('failure added after checking') if rule_error? + end + end + end + + it 'checks for errors in current rule' do + expect(contract.new.(foo: 'some@email.com').errors.to_h).to eql( + foo: ['failure added', 'failure added after checking'] + ) + end + end +end