Hey,
I’m having a problem with combining dry-validation
with a custom type of dry-type
defined with .constructor
.
I’ve created an example with two types. One built with .constructor
and the second with .constrained
, both of them implements the same behaviour. Then I use those types with dry-validation
expecting the same behaviour for each type.
It turns out when you use Dry::Types[Constructor]
type with Dry::Validation::Contract
, the contract won’t fail even though the type raises Dry::Types::ConstraintError
. It looks like this happens because Dry::Validation
provides it’s own fallback block when Dry::Types[Constructor]
is used but does not do that with Dry::Types[Constrained]
.
It seems to be a bit inconsistent. Is it a desire bahaviour or am I missing something here?
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'dry-validation', '1.4.2'
gem 'rspec', '3.9.0'
end
require 'dry-validation'
module Types
include Dry::Types(default: :nominal)
URL_REGEX = /\Awww\.(.*)\z/
ConstrainedUrl = String.constrained(format: URL_REGEX)
ConstructorUrl = String.constructor do |input, &block|
if input.match?(URL_REGEX)
input
elsif block
block.call
else
raise Dry::Types::ConstraintError.new("format?(#{URL_REGEX.inspect}, #{input.inspect})", input)
end
end
end
RSpec.describe 'Passing errors from dry-types to dry-validation', :aggregate_failures do
let(:valid_url) { 'www.wp.pl' }
let(:invalid_url) { 'wp.pl' }
describe 'Types definitions' do
shared_examples 'dry-type interface' do |type|
describe "Type: #{type}" do
context 'with invalid input' do
let(:url) { invalid_url }
it 'Throws Dry::Types::ConstraintError' do
expect { type.call(url) }
.to raise_error(Dry::Types::ConstraintError, "#{url.inspect} violates constraints (format?(/\\Awww\\.(.*)\\z/, #{url.inspect}) failed)")
end
context 'with block provided to the type' do
let(:fallback) { :invalid }
it 'returns fallback value' do
expect(type.call(url) { fallback }).to eq(fallback)
end
end
end
context 'with valid input' do
let(:url) { valid_url }
it 'returns the input' do
expect(type.call(url)).to eq(url)
end
context 'with a block provided to the type' do
let(:fallback) { :invalid }
it 'returns the input' do
expect(type.call(url) { fallback }).to eq(url)
end
end
end
end
end
it_behaves_like 'dry-type interface', Types::ConstrainedUrl
it_behaves_like 'dry-type interface', Types::ConstructorUrl
end
describe 'Validation::Contract definition' do
shared_examples 'contract with typed input' do |type|
describe "Type: #{type}" do
subject(:result) { contract.call(url: url) }
let(:contract) do
Class.new(Dry::Validation::Contract) do
params do
required(:url).value(type)
end
end.new
end
context 'with invalid url' do
let(:url) { invalid_url }
it { is_expected.to_not be_success }
it 'returns errors' do
expect(result.errors.to_h).to eq(
url: ['is in invalid format']
)
end
end
context 'with valid url' do
let(:url) { valid_url }
it { is_expected.to be_success }
it 'returns no errors' do
expect(result.errors).to be_empty
end
end
end
end
it_behaves_like 'contract with typed input', Types::ConstrainedUrl
it_behaves_like 'contract with typed input', Types::ConstructorUrl
end
end
RSpec::Core::Runner.run(['spec', '--format', 'doc'])
# $ ruby dry_validation_spec.rb
#
# Passing errors from dry-types to dry-validation
# Types definitions
# behaves like dry-type interface
# Type: #<Dry::Types[Constrained<Nominal<String> rule=[format?(/\Awww\.(.*)\z/)]>]>
# with invalid input
# Throws Dry::Types::ConstraintError
# with block provided to the type
# returns fallback value
# with valid input
# returns the input
# with a block provided to the type
# returns the input
# behaves like dry-type interface
# Type: #<Dry::Types[Constructor<Nominal<String> fn=dry_validation_spec.rb:17>]>
# with invalid input
# Throws Dry::Types::ConstraintError
# with block provided to the type
# returns fallback value
# with valid input
# returns the input
# with a block provided to the type
# returns the input
# Validation::Contract definition
# behaves like contract with typed input
# Type: #<Dry::Types[Constrained<Nominal<String> rule=[format?(/\Awww\.(.*)\z/)]>]>
# with invalid url
# is expected not to be success
# returns errors
# with valid url
# is expected to be success
# returns no errors
# behaves like contract with typed input
# Type: #<Dry::Types[Constructor<Nominal<String> fn=dry_validation_spec.rb:17>]>
# with invalid url
# is expected not to be success (FAILED - 1)
# returns errors (FAILED - 2)
# with valid url
# is expected to be success
# returns no errors
#
# Failures:
#
# 1) Passing errors from dry-types to dry-validation Validation::Contract definition behaves like contract with typed input Type: #<Dry::Types[Constructor<Nominal<String> fn=dry_validation_spec.rb:17>]> with invalid url is expected not to be success
# Failure/Error: it { is_expected.to_not be_success }
# expected `#<Dry::Validation::Result{:url=>"wp.pl"} errors={}>.success?` to return false, got true
# Shared Example Group: "contract with typed input" called from dry_validation_spec.rb:112
# # dry_validation_spec.rb:90:in `block (6 levels) in <main>'
# # dry_validation_spec.rb:116:in `<main>'
#
# 2) Passing errors from dry-types to dry-validation Validation::Contract definition behaves like contract with typed input Type: #<Dry::Types[Constructor<Nominal<String> fn=dry_validation_spec.rb:17>]> with invalid url returns errors
# Failure/Error:
# expect(result.errors.to_h).to eq(
# url: ['is in invalid format']
# )
#
# expected: {:url=>["is in invalid format"]}
# got: {}
#
# (compared using ==)
#
# Diff:
# @@ -1,2 +1 @@
# -:url => ["is in invalid format"],
#
# Shared Example Group: "contract with typed input" called from dry_validation_spec.rb:112
# # dry_validation_spec.rb:93:in `block (6 levels) in <main>'
# # dry_validation_spec.rb:116:in `<main>'
#
# Finished in 0.04198 seconds (files took 0.08128 seconds to load)
# 16 examples, 2 failures
#
# Failed examples:
#
# rspec 'dry_validation_spec.rb[1:2:2:1:1:1]' # Passing errors from dry-types to dry-validation Validation::Contract definition behaves like contract with typed input Type: #<Dry::Types[Constructor<Nominal<String> fn=dry_validation_spec.rb:17>]> with invalid url is expected not to be success
# rspec 'dry_validation_spec.rb[1:2:2:1:1:2]' # Passing errors from dry-types to dry-validation Validation::Contract definition behaves like contract with typed input Type: #<Dry::Types[Constructor<Nominal<String> fn=dry_validation_spec.rb:17>]> with invalid url returns errors