rubocop -a

This commit is contained in:
Rob Hanlon 2020-03-10 20:13:41 -07:00
parent 016b634b8c
commit 9161759d83
69 changed files with 732 additions and 732 deletions

36
Gemfile
View File

@ -1,37 +1,37 @@
# frozen_string_literal: true # frozen_string_literal: true
source 'https://rubygems.org' source "https://rubygems.org"
eval_gemfile 'Gemfile.devtools' eval_gemfile "Gemfile.devtools"
gemspec gemspec
if ENV['DRY_CONFIGURABLE_FROM_MASTER'].eql?('true') if ENV["DRY_CONFIGURABLE_FROM_MASTER"].eql?("true")
gem 'dry-configurable', github: 'dry-rb/dry-configurable', branch: 'master' gem "dry-configurable", github: "dry-rb/dry-configurable", branch: "master"
end end
gem 'dry-schema', github: 'dry-rb/dry-schema', branch: 'master' gem "dry-schema", github: "dry-rb/dry-schema", branch: "master"
if ENV['DRY_TYPES_FROM_MASTER'].eql?('true') if ENV["DRY_TYPES_FROM_MASTER"].eql?("true")
gem 'dry-types', github: 'dry-rb/dry-types', branch: 'master' gem "dry-types", github: "dry-rb/dry-types", branch: "master"
end end
group :test do group :test do
gem 'dry-monads', '~> 1.0' gem "dry-monads", "~> 1.0"
gem 'i18n', require: false gem "i18n", require: false
end end
group :tools do group :tools do
gem 'pry', platform: :jruby gem "pry", platform: :jruby
gem 'pry-byebug', platform: :mri gem "pry-byebug", platform: :mri
end end
group :benchmarks do group :benchmarks do
gem 'actionpack' gem "actionpack"
gem 'activemodel' gem "activemodel"
gem 'activerecord' gem "activerecord"
gem 'benchmark-ips' gem "benchmark-ips"
gem 'hotch', platform: :mri gem "hotch", platform: :mri
gem 'sqlite3' gem "sqlite3"
gem 'virtus' gem "virtus"
end end

View File

@ -1,22 +1,22 @@
#!/usr/bin/env rake #!/usr/bin/env rake
# frozen_string_literal: true # frozen_string_literal: true
require 'bundler/gem_tasks' require "bundler/gem_tasks"
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib')) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib"))
require 'rspec/core' require "rspec/core"
require 'rspec/core/rake_task' require "rspec/core/rake_task"
require 'dry/validation' require "dry/validation"
RSpec::Core::RakeTask.new(:spec) RSpec::Core::RakeTask.new(:spec)
extensions = Dir['./spec/extensions/*'].map { |path| Pathname(path).basename.to_s.to_sym } extensions = Dir["./spec/extensions/*"].map { |path| Pathname(path).basename.to_s.to_sym }
desc 'Run only core specs without extensions specs' desc "Run only core specs without extensions specs"
RSpec::Core::RakeTask.new('spec:core') do |t| RSpec::Core::RakeTask.new("spec:core") do |t|
t.rspec_opts = 'spec/unit spec/integration --pattern **/*_spec.rb' t.rspec_opts = "spec/unit spec/integration --pattern **/*_spec.rb"
end end
extensions.each do |ext| extensions.each do |ext|
@ -30,18 +30,18 @@ extensions.each do |ext|
end end
end end
desc 'Run all specs with all extensions enabled' desc "Run all specs with all extensions enabled"
task 'spec:extensions' do task "spec:extensions" do
puts "Loading extensions: #{extensions.inspect}" puts "Loading extensions: #{extensions.inspect}"
Dry::Validation.load_extensions(*extensions) Dry::Validation.load_extensions(*extensions)
Rake::Task[:spec].invoke Rake::Task[:spec].invoke
end end
desc 'Run all specs in isolation with extension enabled' desc "Run all specs in isolation with extension enabled"
task 'spec:isolation' => ['spec:core', *extensions.map { |ext| "spec:#{ext}" }] task "spec:isolation" => ["spec:core", *extensions.map { |ext| "spec:#{ext}" }]
desc 'Run CI build' desc "Run CI build"
task ci: %w[spec:core spec:isolation spec:extensions] task ci: %w[spec:core spec:isolation spec:extensions]
task default: :ci task default: :ci

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'active_record' require "active_record"
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do ActiveRecord::Schema.define do
create_table :users do |table| create_table :users do |table|
@ -16,6 +16,6 @@ module AR
self.table_name = :users self.table_name = :users
validates :email, :age, presence: true validates :email, :age, presence: true
validates :age, numericality: { greater_than: 18 } validates :age, numericality: {greater_than: 18}
end end
end end

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'benchmark/ips' require "benchmark/ips"
require 'active_model' require "active_model"
require 'i18n' require "i18n"
require 'dry-validation' require "dry-validation"
require 'byebug' require "byebug"
require_relative 'active_record_setup' require_relative "active_record_setup"
module AM module AM
class User class User
@ -16,10 +16,10 @@ module AM
attr_reader :email, :age attr_reader :email, :age
validates :email, :age, presence: true validates :email, :age, presence: true
validates :age, numericality: { greater_than: 18 } validates :age, numericality: {greater_than: 18}
def initialize(attrs) def initialize(attrs)
@email, @age = attrs.values_at('email', 'age') @email, @age = attrs.values_at("email", "age")
end end
end end
end end
@ -33,30 +33,30 @@ contract = Dry::Validation::Contract.build do
end end
rule(:age) do rule(:age) do
key.failure('must be greater than 18') if values[:age] <= 18 key.failure("must be greater than 18") if values[:age] <= 18
end end
end end
params = { 'email' => '', 'age' => '18' } params = {"email" => "", "age" => "18"}
puts contract.(params).inspect puts contract.(params).inspect
puts AR::User.new(params).validate puts AR::User.new(params).validate
puts AM::User.new(params).validate puts AM::User.new(params).validate
Benchmark.ips do |x| Benchmark.ips do |x|
x.report('ActiveModel') do x.report("ActiveModel") do
user = AM::User.new(params) user = AM::User.new(params)
user.validate user.validate
user.errors user.errors
end end
x.report('ActiveRecord') do x.report("ActiveRecord") do
user = AR::User.new(params) user = AR::User.new(params)
user.validate user.validate
user.errors user.errors
end end
x.report('dry-validation') do x.report("dry-validation") do
contract.(params).errors contract.(params).errors
end end

View File

@ -1,20 +1,20 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'benchmark/ips' require "benchmark/ips"
require 'active_model' require "active_model"
require 'i18n' require "i18n"
require 'dry-validation' require "dry-validation"
require 'byebug' require "byebug"
COUNT = (ENV['COUNT'] || 100).to_i COUNT = (ENV["COUNT"] || 100).to_i
FIELDS = COUNT.times.map { |i| :"field_#{i}" } FIELDS = COUNT.times.map { |i| :"field_#{i}" }
class User class User
include ActiveModel::Validations include ActiveModel::Validations
attr_reader(*FIELDS) attr_reader(*FIELDS)
validates(*FIELDS, presence: true, numericality: { greater_than: FIELDS.size / 2 }) validates(*FIELDS, presence: true, numericality: {greater_than: FIELDS.size / 2})
def initialize(attrs) def initialize(attrs)
attrs.each do |field, value| attrs.each do |field, value|
@ -39,13 +39,13 @@ puts contract.(params).inspect
puts User.new(params).validate puts User.new(params).validate
Benchmark.ips do |x| Benchmark.ips do |x|
x.report('ActiveModel::Validations') do x.report("ActiveModel::Validations") do
user = User.new(params) user = User.new(params)
user.validate user.validate
user.errors user.errors
end end
x.report('dry-validation / schema') do x.report("dry-validation / schema") do
contract.(params).errors contract.(params).errors
end end

View File

@ -1,12 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'benchmark/ips' require "benchmark/ips"
require 'active_model' require "active_model"
require 'i18n' require "i18n"
require 'dry-validation' require "dry-validation"
require_relative 'active_record_setup' require_relative "active_record_setup"
module AM module AM
class User class User
@ -15,10 +15,10 @@ module AM
attr_reader :email, :age attr_reader :email, :age
validates :email, :age, presence: true validates :email, :age, presence: true
validates :age, presence: true, numericality: { greater_than: 18 } validates :age, presence: true, numericality: {greater_than: 18}
def initialize(attrs) def initialize(attrs)
@email, @age = attrs.values_at('email', 'age') @email, @age = attrs.values_at("email", "age")
end end
end end
end end
@ -32,29 +32,29 @@ contract = Dry::Validation::Contract.build {
end end
rule(:age) do rule(:age) do
failure('must be greater than 18') if values[:age] <= 18 failure("must be greater than 18") if values[:age] <= 18
end end
} }
params = { 'email' => 'jane@doe.org', 'age' => '19' } params = {"email" => "jane@doe.org", "age" => "19"}
puts contract.(params).inspect puts contract.(params).inspect
puts AM::User.new(params).validate puts AM::User.new(params).validate
Benchmark.ips do |x| Benchmark.ips do |x|
x.report('ActiveModel::Validations') do x.report("ActiveModel::Validations") do
user = AM::User.new(params) user = AM::User.new(params)
user.validate user.validate
user.errors user.errors
end end
x.report('ActiveRecord') do x.report("ActiveRecord") do
user = AR::User.new(params) user = AR::User.new(params)
user.validate user.validate
user.errors user.errors
end end
x.report('dry-validation') do x.report("dry-validation") do
contract.(params).errors contract.(params).errors
end end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'suite' require_relative "suite"
class TestContract < Dry::Validation::Contract class TestContract < Dry::Validation::Contract
config.messages.backend = :i18n config.messages.backend = :i18n
@ -12,12 +12,12 @@ class TestContract < Dry::Validation::Contract
end end
rule(:age) do rule(:age) do
key.failure('must be greater than 18') if values[:age] <= 18 key.failure("must be greater than 18") if values[:age] <= 18
end end
end end
contract = TestContract.new contract = TestContract.new
input = { email: '', age: 18, address: {} } input = {email: "", age: 18, address: {}}
profile do profile do
10_000.times do 10_000.times do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'suite' require_relative "suite"
class TestContract < Dry::Validation::Contract class TestContract < Dry::Validation::Contract
config.messages.backend = :i18n config.messages.backend = :i18n
@ -12,11 +12,11 @@ class TestContract < Dry::Validation::Contract
end end
rule(:age) do rule(:age) do
key.failure('must be greater than 18') if values[:age] <= 18 key.failure("must be greater than 18") if values[:age] <= 18
end end
end end
input = { 'email' => 'jane@doe.org', 'age' => 19, 'address' => { 'city' => 'Krakow' } } input = {"email" => "jane@doe.org", "age" => 19, "address" => {"city" => "Krakow"}}
contract = TestContract.new contract = TestContract.new
profile do profile do

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'suite' require_relative "suite"
require 'hotch' require "hotch"
Hotch() do Hotch() do
1000.times do 1000.times do

View File

@ -1,14 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'suite' require_relative "suite"
require 'hotch' require "hotch"
require 'dry-validation' require "dry-validation"
I18n.locale = :en I18n.locale = :en
I18n.backend.load_translations I18n.backend.load_translations
COUNT = ENV['COUNT'].to_i COUNT = ENV["COUNT"].to_i
FIELDS = COUNT.times.map { |i| :"field_#{i}" } FIELDS = COUNT.times.map { |i| :"field_#{i}" }
schema = Dry::Validation.Schema do schema = Dry::Validation.Schema do

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'suite' require_relative "suite"
require 'hotch' require "hotch"
schema = Dry::Validation.Schema do schema = Dry::Validation.Schema do
configure { config.messages = :i18n } configure { config.messages = :i18n }
@ -11,7 +11,7 @@ schema = Dry::Validation.Schema do
required(:address).filled(:hash?) required(:address).filled(:hash?)
end end
input = { email: '', age: 18, address: {} } input = {email: "", age: 18, address: {}}
puts schema.(input).inspect puts schema.(input).inspect

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'hotch' require "hotch"
require 'i18n' require "i18n"
require 'dry-validation' require "dry-validation"
def profile(&block) def profile(&block)
Hotch(filter: 'Dry', &block) Hotch(filter: "Dry", &block)
end end

View File

@ -2,9 +2,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'pry' require "pry"
require 'bundler/setup' require "bundler/setup"
require 'dry-validation' require "dry-validation"
module Types module Types
include Dry::Types() include Dry::Types()
@ -28,4 +28,4 @@ class Context
end end
end end
Pry.start(Context.new, prompt: [proc { 'dry-validation> ' }, proc { 'dry-validation*> ' }]) Pry.start(Context.new, prompt: [proc { "dry-validation> " }, proc { "dry-validation*> " }])

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry-validation' require "dry-validation"
schema = Dry::Validation.Schema do schema = Dry::Validation.Schema do
required(:email).filled required(:email).filled
@ -8,7 +8,7 @@ schema = Dry::Validation.Schema do
required(:age).filled(:int?, gt?: 18) required(:age).filled(:int?, gt?: 18)
end end
errors = schema.call(email: 'jane@doe.org', age: 19).messages errors = schema.call(email: "jane@doe.org", age: 19).messages
puts errors.inspect puts errors.inspect

View File

@ -1,16 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'byebug' require "byebug"
require 'dry-validation' require "dry-validation"
schema = Dry::Validation.Schema do schema = Dry::Validation.Schema do
key(:phone_numbers).each(:str?) key(:phone_numbers).each(:str?)
end end
errors = schema.call(phone_numbers: '').messages errors = schema.call(phone_numbers: "").messages
puts errors.inspect puts errors.inspect
errors = schema.call(phone_numbers: ['123456789', 123_456_789]).messages errors = schema.call(phone_numbers: ["123456789", 123_456_789]).messages
puts errors.inspect puts errors.inspect

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'json' require "json"
require 'dry-validation' require "dry-validation"
contract = Class.new(Dry::Validation::Contract) do contract = Class.new(Dry::Validation::Contract) do
json do json do

View File

@ -1,14 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry-validation' require "dry-validation"
schema = Dry::Validation.Schema do schema = Dry::Validation.Schema do
configure do configure do
def self.messages def self.messages
super.merge(en: { super.merge(en: {
errors: { errors: {
john_email?: '%{value} is not a john email', john_email?: "%{value} is not a john email",
example_email?: '%{value} is not a example email' example_email?: "%{value} is not a example email"
} }
}) })
end end
@ -17,14 +17,14 @@ schema = Dry::Validation.Schema do
required(:email).filled required(:email).filled
validate(example_email?: :email) do |value| validate(example_email?: :email) do |value|
value.end_with?('@example.com') value.end_with?("@example.com")
end end
validate(john_email?: :email) do |value| validate(john_email?: :email) do |value|
value.start_with?('john') value.start_with?("john")
end end
end end
errors = schema.call(email: 'jane@doe.org').messages errors = schema.call(email: "jane@doe.org").messages
puts errors.inspect puts errors.inspect

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry-validation' require "dry-validation"
schema = Dry::Validation.Schema do schema = Dry::Validation.Schema do
required(:address).schema do required(:address).schema do
@ -19,6 +19,6 @@ errors = schema.call({}).messages
puts errors.inspect puts errors.inspect
errors = schema.call(address: { city: 'NYC' }).messages errors = schema.call(address: {city: "NYC"}).messages
puts errors.inspect puts errors.inspect

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry-validation' require "dry-validation"
contract = Class.new(Dry::Validation::Contract) do contract = Class.new(Dry::Validation::Contract) do
params do params do
@ -9,6 +9,6 @@ contract = Class.new(Dry::Validation::Contract) do
end end
end.new end.new
result = contract.('email' => '', 'age' => '19') result = contract.("email" => "", "age" => "19")
puts result.inspect puts result.inspect

View File

@ -1,3 +1,3 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation' require "dry/validation"

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/constants' require "dry/validation/constants"
require 'dry/validation/contract' require "dry/validation/contract"
require 'dry/validation/macros' require "dry/validation/macros"
# Main namespace # Main namespace
# #
@ -16,15 +16,15 @@ module Dry
extend Macros::Registrar extend Macros::Registrar
register_extension(:monads) do register_extension(:monads) do
require 'dry/validation/extensions/monads' require "dry/validation/extensions/monads"
end end
register_extension(:hints) do register_extension(:hints) do
require 'dry/validation/extensions/hints' require "dry/validation/extensions/hints"
end end
register_extension(:predicates_as_macros) do register_extension(:predicates_as_macros) do
require 'dry/validation/extensions/predicates_as_macros' require "dry/validation/extensions/predicates_as_macros"
end end
# Define a contract and build its instance # Define a contract and build its instance

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/schema/config' require "dry/schema/config"
require 'dry/validation/macros' require "dry/validation/macros"
module Dry module Dry
module Validation module Validation

View File

@ -1,22 +1,22 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'pathname' require "pathname"
require 'dry/core/constants' require "dry/core/constants"
module Dry module Dry
module Validation module Validation
include Dry::Core::Constants include Dry::Core::Constants
DOT = '.' DOT = "."
# Root path is used for base errors in hash representation of error messages # Root path is used for base errors in hash representation of error messages
ROOT_PATH = [nil].freeze ROOT_PATH = [nil].freeze
# Path to the default errors locale file # Path to the default errors locale file
DEFAULT_ERRORS_NAMESPACE = 'dry_validation' DEFAULT_ERRORS_NAMESPACE = "dry_validation"
# Path to the default errors locale file # Path to the default errors locale file
DEFAULT_ERRORS_PATH = Pathname(__FILE__).join('../../../../config/errors.yml').realpath.freeze DEFAULT_ERRORS_PATH = Pathname(__FILE__).join("../../../../config/errors.yml").realpath.freeze
# Mapping for block kwarg options used by block_options # Mapping for block kwarg options used by block_options
# #

View File

@ -1,18 +1,18 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'concurrent/map' require "concurrent/map"
require 'dry/equalizer' require "dry/equalizer"
require 'dry/initializer' require "dry/initializer"
require 'dry/schema/path' require "dry/schema/path"
require 'dry/validation/config' require "dry/validation/config"
require 'dry/validation/constants' require "dry/validation/constants"
require 'dry/validation/rule' require "dry/validation/rule"
require 'dry/validation/evaluator' require "dry/validation/evaluator"
require 'dry/validation/messages/resolver' require "dry/validation/messages/resolver"
require 'dry/validation/result' require "dry/validation/result"
require 'dry/validation/contract/class_interface' require "dry/validation/contract/class_interface"
module Dry module Dry
module Validation module Validation

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/schema' require "dry/schema"
require 'dry/schema/messages' require "dry/schema/messages"
require 'dry/schema/path' require "dry/schema/path"
require 'dry/schema/key_map' require "dry/schema/key_map"
require 'dry/validation/constants' require "dry/validation/constants"
require 'dry/validation/macros' require "dry/validation/macros"
require 'dry/validation/schema_ext' require "dry/validation/schema_ext"
module Dry module Dry
module Validation module Validation
@ -23,7 +23,7 @@ module Dry
# @api private # @api private
def inherited(klass) def inherited(klass)
super super
klass.instance_variable_set('@config', config.dup) klass.instance_variable_set("@config", config.dup)
end end
# Configuration # Configuration
@ -190,7 +190,7 @@ module Dry
# @api private # @api private
def core_schema_opts def core_schema_opts
{ parent: superclass&.__schema__, config: config } {parent: superclass&.__schema__, config: config}
end end
# @api private # @api private
@ -198,7 +198,7 @@ module Dry
return __schema__ if external_schemas.empty? && block.nil? return __schema__ if external_schemas.empty? && block.nil?
unless __schema__.nil? unless __schema__.nil?
raise ::Dry::Validation::DuplicateSchemaError, 'Schema has already been defined' raise ::Dry::Validation::DuplicateSchemaError, "Schema has already been defined"
end end
schema_opts = core_schema_opts schema_opts = core_schema_opts

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/initializer' require "dry/initializer"
require 'dry/core/deprecations' require "dry/core/deprecations"
require 'dry/validation/constants' require "dry/validation/constants"
require 'dry/validation/failures' require "dry/validation/failures"
module Dry module Dry
module Validation module Validation

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/monads/result' require "dry/monads/result"
module Dry module Dry
module Validation module Validation

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/monads/result' require "dry/monads/result"
module Dry module Dry
module Validation module Validation

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/schema/predicate_registry' require "dry/schema/predicate_registry"
require 'dry/validation/contract' require "dry/validation/contract"
module Dry module Dry
module Validation module Validation

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/schema/path' require "dry/schema/path"
require 'dry/validation/constants' require "dry/validation/constants"
module Dry module Dry
module Validation module Validation
@ -57,7 +57,7 @@ module Dry
# #
# @api public # @api public
def failure(message, tokens = EMPTY_HASH) def failure(message, tokens = EMPTY_HASH)
opts << { message: message, tokens: tokens, path: path } opts << {message: message, tokens: tokens, path: path}
self self
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/initializer' require "dry/initializer"
require 'dry/validation/constants' require "dry/validation/constants"
module Dry module Dry
module Validation module Validation

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/constants' require "dry/validation/constants"
require 'dry/validation/function' require "dry/validation/function"
module Dry module Dry
module Validation module Validation

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/container' require "dry/container"
require 'dry/validation/macro' require "dry/validation/macro"
module Dry module Dry
module Validation module Validation

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/equalizer' require "dry/equalizer"
require 'dry/schema/constants' require "dry/schema/constants"
require 'dry/schema/message' require "dry/schema/message"
module Dry module Dry
module Validation module Validation

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/schema/message_set' require "dry/schema/message_set"
require 'dry/validation/constants' require "dry/validation/constants"
require 'dry/validation/message' require "dry/validation/message"
module Dry module Dry
module Validation module Validation

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/message' require "dry/validation/message"
module Dry module Dry
module Validation module Validation
@ -95,7 +95,7 @@ module Dry
def parse_token(token) def parse_token(token)
case token case token
when Array when Array
token.join(', ') token.join(", ")
else else
token token
end end

View File

@ -1,11 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'concurrent/map' require "concurrent/map"
require 'dry/equalizer' require "dry/equalizer"
require 'dry/validation/constants' require "dry/validation/constants"
require 'dry/validation/message_set' require "dry/validation/message_set"
require 'dry/validation/values' require "dry/validation/values"
module Dry module Dry
module Validation module Validation
@ -179,7 +179,7 @@ module Dry
super super
end end
if RUBY_VERSION >= '2.7' if RUBY_VERSION >= "2.7"
# Pattern matching # Pattern matching
# #
# @api private # @api private

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/equalizer' require "dry/equalizer"
require 'dry/validation/constants' require "dry/validation/constants"
require 'dry/validation/function' require "dry/validation/function"
module Dry module Dry
module Validation module Validation

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/schema/path' require "dry/schema/path"
module Dry module Dry
module Schema module Schema

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/equalizer' require "dry/equalizer"
require 'dry/schema/path' require "dry/schema/path"
require 'dry/validation/constants' require "dry/validation/constants"
module Dry module Dry
module Validation module Validation
@ -53,7 +53,7 @@ module Dry
vals = self.class.new(data.dig(*keys)) vals = self.class.new(data.dig(*keys))
vals.fetch_values(*last) { nil } vals.fetch_values(*last) { nil }
else else
raise ArgumentError, '+key+ must be a valid path specification' raise ArgumentError, "+key+ must be a valid path specification"
end end
end end

View File

@ -2,6 +2,6 @@
module Dry module Dry
module Validation module Validation
VERSION = '1.4.2' VERSION = "1.4.2"
end end
end end

View File

@ -10,53 +10,53 @@ RSpec.describe Dry::Validation::Result do
end end
rule(:name) do rule(:name) do
key.failure('oops') if values[:name] != 'Jane' key.failure("oops") if values[:name] != "Jane"
end end
end.new end.new
end end
let(:result) { schema.(input) } let(:result) { schema.(input) }
context 'with valid input' do context "with valid input" do
let(:input) { { name: 'Jane' } } let(:input) { {name: "Jane"} }
describe '#success?' do describe "#success?" do
it 'returns true' do it "returns true" do
expect(result).to be_success expect(result).to be_success
end end
end end
describe '#hints' do describe "#hints" do
it 'returns an empty array' do it "returns an empty array" do
expect(result.hints).to be_empty expect(result.hints).to be_empty
end end
end end
end end
context 'with invalid input' do context "with invalid input" do
let(:input) { { name: '' } } let(:input) { {name: ""} }
describe '#failure?' do describe "#failure?" do
it 'returns true' do it "returns true" do
expect(result).to be_failure expect(result).to be_failure
end end
end end
describe '#hints' do describe "#hints" do
it 'returns hint messages excluding errors' do it "returns hint messages excluding errors" do
expect(result.hints.to_h).to eql(name: ['length must be within 2 - 4']) expect(result.hints.to_h).to eql(name: ["length must be within 2 - 4"])
end end
end end
describe '#messages' do describe "#messages" do
it 'returns hints + error messages' do it "returns hints + error messages" do
expect(result.messages.to_h).to eql(name: ['must be filled', 'length must be within 2 - 4']) expect(result.messages.to_h).to eql(name: ["must be filled", "length must be within 2 - 4"])
end end
end end
describe '#errors' do describe "#errors" do
it 'returns errors excluding hints' do it "returns errors excluding hints" do
expect(result.errors.to_h).to eql(name: ['must be filled']) expect(result.errors.to_h).to eql(name: ["must be filled"])
end end
end end
end end

View File

@ -13,11 +13,11 @@ RSpec.describe Dry::Validation::Result do
let(:result) { schema.(input) } let(:result) { schema.(input) }
context 'with valid input' do context "with valid input" do
let(:input) { { name: 'Jane' } } let(:input) { {name: "Jane"} }
describe '#to_monad' do describe "#to_monad" do
it 'returns a Success value' do it "returns a Success value" do
monad = result.to_monad monad = result.to_monad
expect(monad).to be_a Dry::Monads::Result expect(monad).to be_a Dry::Monads::Result
@ -27,11 +27,11 @@ RSpec.describe Dry::Validation::Result do
end end
end end
context 'with invalid input' do context "with invalid input" do
let(:input) { { name: '' } } let(:input) { {name: ""} }
describe '#to_monad' do describe "#to_monad" do
it 'returns a Failure value' do it "returns a Failure value" do
monad = result.to_monad monad = result.to_monad
expect(monad).to be_a_failure expect(monad).to be_a_failure

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/extensions/predicates_as_macros' require "dry/validation/extensions/predicates_as_macros"
RSpec.describe Dry::Validation::Contract do RSpec.describe Dry::Validation::Contract do
context 'with predicates_as_macros extension' do context "with predicates_as_macros extension" do
before { Dry::Validation.load_extensions(:predicates_as_macros) } before { Dry::Validation.load_extensions(:predicates_as_macros) }
subject(:contract) do subject(:contract) do
@ -26,7 +26,7 @@ RSpec.describe Dry::Validation::Contract do
end end
end end
it 'macros succeed on predicate success' do it "macros succeed on predicate success" do
age_contract = Class.new(contract) do age_contract = Class.new(contract) do
schema do schema do
required(:age).filled(:integer) required(:age).filled(:integer)
@ -38,7 +38,7 @@ RSpec.describe Dry::Validation::Contract do
expect(age_contract.(age: 19)).to be_success expect(age_contract.(age: 19)).to be_success
end end
it 'macros fail on predicate failure' do it "macros fail on predicate failure" do
age_contract = Class.new(contract) do age_contract = Class.new(contract) do
schema do schema do
required(:age).filled(:integer) required(:age).filled(:integer)
@ -50,7 +50,7 @@ RSpec.describe Dry::Validation::Contract do
expect(age_contract.(age: 17)).to be_failure expect(age_contract.(age: 17)).to be_failure
end end
it 'failure message is built from predicate name and arguments' do it "failure message is built from predicate name and arguments" do
age_contract = Class.new(contract) do age_contract = Class.new(contract) do
schema do schema do
required(:age).filled(:integer) required(:age).filled(:integer)
@ -63,7 +63,7 @@ RSpec.describe Dry::Validation::Contract do
expect( expect(
result.errors.first.text result.errors.first.text
).to eq('must be greater than or equal to 18') ).to eq("must be greater than or equal to 18")
end end
end end
end end

View File

@ -1,12 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, '#call' do RSpec.describe Dry::Validation::Contract, "#call" do
subject(:contract) do subject(:contract) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
params do params do
@ -27,111 +27,111 @@ RSpec.describe Dry::Validation::Contract, '#call' do
rule(:login) do rule(:login) do
if key? if key?
key.failure('too short') if value.length < 3 key.failure("too short") if value.length < 3
end end
end end
rule(address: { geolocation: [:lon, :lat] }) do rule(address: {geolocation: [:lon, :lat]}) do
if key? if key?
lon, lat = value lon, lat = value
key('address.geolocation.lat').failure('invalid') if lat < 10 key("address.geolocation.lat").failure("invalid") if lat < 10
key('address.geolocation.lon').failure('invalid') if lon < 10 key("address.geolocation.lon").failure("invalid") if lon < 10
end end
end end
rule(:password) do rule(:password) do
key.failure('is required') if values[:login] && !values[:password] key.failure("is required") if values[:login] && !values[:password]
end end
rule(:age) do rule(:age) do
key.failure('must be greater or equal 18') if value < 18 key.failure("must be greater or equal 18") if value < 18
end end
rule(:age) do rule(:age) do
key.failure('must be greater than 0') if value < 0 key.failure("must be greater than 0") if value < 0
end end
rule(address: :zip) do rule(address: :zip) do
address = values[:address] address = values[:address]
if address && address[:country] == 'Russia' && address[:zip] != /\A\d{6}\z/ if address && address[:country] == "Russia" && address[:zip] != /\A\d{6}\z/
key.failure('must have 6 digit') key.failure("must have 6 digit")
end end
end end
rule('address.geolocation.lon') do rule("address.geolocation.lon") do
key.failure('invalid longitude') if key? && !(-180.0...180.0).cover?(value) key.failure("invalid longitude") if key? && !(-180.0...180.0).cover?(value)
end end
end.new end.new
end end
it 'applies rule to input processed by the schema' do it "applies rule to input processed by the schema" do
result = contract.(email: 'john@doe.org', age: 19) result = contract.(email: "john@doe.org", age: 19)
expect(result).to be_success expect(result).to be_success
expect(result.errors.to_h).to eql({}) expect(result.errors.to_h).to eql({})
end end
it 'applies rule to an optional field when value is present' do it "applies rule to an optional field when value is present" do
result = contract.(email: 'john@doe.org', age: 19, login: 'ab') result = contract.(email: "john@doe.org", age: 19, login: "ab")
expect(result).to be_failure expect(result).to be_failure
expect(result.errors.to_h).to eql(login: ['too short'], password: ['is required']) expect(result.errors.to_h).to eql(login: ["too short"], password: ["is required"])
end end
it 'applies rule to an optional nested field when value is present' do it "applies rule to an optional nested field when value is present" do
result = contract.( result = contract.(
email: 'john@doe.org', email: "john@doe.org",
age: 19, age: 19,
address: { address: {
geolocation: { lat: 11, lon: 1 }, geolocation: {lat: 11, lon: 1},
country: 'Poland', country: "Poland",
zip: '12345' zip: "12345"
} }
) )
expect(result).to be_failure expect(result).to be_failure
expect(result.errors.to_h).to eql(address: { geolocation: { lon: ['invalid'] } }) expect(result.errors.to_h).to eql(address: {geolocation: {lon: ["invalid"]}})
end end
it 'returns rule errors' do it "returns rule errors" do
result = contract.(email: 'john@doe.org', login: 'jane', age: 19) result = contract.(email: "john@doe.org", login: "jane", age: 19)
expect(result).to be_failure expect(result).to be_failure
expect(result.errors.to_h).to eql(password: ['is required']) expect(result.errors.to_h).to eql(password: ["is required"])
end end
it "doesn't execute rules when basic checks failed" do it "doesn't execute rules when basic checks failed" do
result = contract.(email: 'john@doe.org', age: 'not-an-integer') result = contract.(email: "john@doe.org", age: "not-an-integer")
expect(result).to be_failure expect(result).to be_failure
expect(result.errors.to_h).to eql(age: ['must be an integer']) expect(result.errors.to_h).to eql(age: ["must be an integer"])
end end
it 'gathers errors from multiple rules for the same key' do it "gathers errors from multiple rules for the same key" do
result = contract.(email: 'john@doe.org', age: -1) result = contract.(email: "john@doe.org", age: -1)
expect(result).to be_failure expect(result).to be_failure
expect(result.errors.to_h).to eql(age: ['must be greater or equal 18', 'must be greater than 0']) expect(result.errors.to_h).to eql(age: ["must be greater or equal 18", "must be greater than 0"])
end end
it 'builds nested message keys for nested rules' do it "builds nested message keys for nested rules" do
result = contract.(email: 'john@doe.org', age: 20, address: { country: 'Russia', zip: 'abc' }) result = contract.(email: "john@doe.org", age: 20, address: {country: "Russia", zip: "abc"})
expect(result).to be_failure expect(result).to be_failure
expect(result.errors.to_h).to eql(address: { zip: ['must have 6 digit'] }) expect(result.errors.to_h).to eql(address: {zip: ["must have 6 digit"]})
end end
it 'builds deeply nested messages for deeply nested rules' do it "builds deeply nested messages for deeply nested rules" do
result = contract.( result = contract.(
email: 'john@doe.org', email: "john@doe.org",
age: 20, age: 20,
address: { address: {
country: 'UK', zip: 'irrelevant', country: "UK", zip: "irrelevant",
geolocation: { lon: '365', lat: '78' } geolocation: {lon: "365", lat: "78"}
} }
) )
expect(result).to be_failure expect(result).to be_failure
expect(result.errors.to_h).to eql(address: { geolocation: { lon: ['invalid longitude'] } }) expect(result.errors.to_h).to eql(address: {geolocation: {lon: ["invalid longitude"]}})
end end
end end

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation, '.Contract' do RSpec.describe Dry::Validation, ".Contract" do
subject(:contract) do subject(:contract) do
Dry::Validation.Contract do Dry::Validation.Contract do
params do params do
@ -11,7 +11,7 @@ RSpec.describe Dry::Validation, '.Contract' do
end end
end end
it 'returns an instance of Dry::Validation::Contract' do it "returns an instance of Dry::Validation::Contract" do
expect(contract).to be_a(Dry::Validation::Contract) expect(contract).to be_a(Dry::Validation::Contract)
end end
end end

View File

@ -1,25 +1,25 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, 'setting default locale' do RSpec.describe Dry::Validation::Contract, "setting default locale" do
subject(:contract) do subject(:contract) do
Dry::Validation.Contract do Dry::Validation.Contract do
config.messages.default_locale = :pl config.messages.default_locale = :pl
config.messages.backend = :i18n config.messages.backend = :i18n
config.messages.load_paths << SPEC_ROOT.join('fixtures/messages/errors.pl.yml') config.messages.load_paths << SPEC_ROOT.join("fixtures/messages/errors.pl.yml")
params do params do
required(:email).filled(:string) required(:email).filled(:string)
end end
rule(:email) do rule(:email) do
key.failure(:invalid) unless values[:email].include?('@') key.failure(:invalid) unless values[:email].include?("@")
end end
end end
end end
it 'uses configured default locale' do it "uses configured default locale" do
expect(contract.(email: 'foo').errors.to_h).to eql(email: ['oh nie zły email']) expect(contract.(email: "foo").errors.to_h).to eql(email: ["oh nie zły email"])
end end
end end

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, '.json' do RSpec.describe Dry::Validation::Contract, ".json" do
subject(:contract_class) do subject(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
json do json do
@ -11,19 +11,19 @@ RSpec.describe Dry::Validation::Contract, '.json' do
end end
end end
it 'defines a JSON schema' do it "defines a JSON schema" do
expect(contract_class.schema).to be_a(Dry::Schema::JSON) expect(contract_class.schema).to be_a(Dry::Schema::JSON)
expect(contract_class.json).to be_a(Dry::Schema::JSON) expect(contract_class.json).to be_a(Dry::Schema::JSON)
expect(contract_class.schema).to be(contract_class.json) expect(contract_class.schema).to be(contract_class.json)
end end
it 'returns nil if schema is not defined' do it "returns nil if schema is not defined" do
contract_class = Class.new(Dry::Validation::Contract) contract_class = Class.new(Dry::Validation::Contract)
expect(contract_class.schema).to be(nil) expect(contract_class.schema).to be(nil)
end end
it 'raises an error if schema is already defined' do it "raises an error if schema is already defined" do
expect do expect do
contract_class.json do contract_class.json do
required(:login).filled(:string) required(:login).filled(:string)

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, '.params' do RSpec.describe Dry::Validation::Contract, ".params" do
subject(:contract_class) do subject(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
params do params do
@ -11,19 +11,19 @@ RSpec.describe Dry::Validation::Contract, '.params' do
end end
end end
it 'defines a Params schema' do it "defines a Params schema" do
expect(contract_class.schema).to be_a(Dry::Schema::Params) expect(contract_class.schema).to be_a(Dry::Schema::Params)
expect(contract_class.params).to be_a(Dry::Schema::Params) expect(contract_class.params).to be_a(Dry::Schema::Params)
expect(contract_class.schema).to be(contract_class.params) expect(contract_class.schema).to be(contract_class.params)
end end
it 'returns nil if schema is not defined' do it "returns nil if schema is not defined" do
contract_class = Class.new(Dry::Validation::Contract) contract_class = Class.new(Dry::Validation::Contract)
expect(contract_class.schema).to be(nil) expect(contract_class.schema).to be(nil)
end end
it 'raises an error if schema is already defined' do it "raises an error if schema is already defined" do
expect do expect do
contract_class.params do contract_class.params do
required(:login).filled(:string) required(:login).filled(:string)

View File

@ -1,15 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, 'Rule#each' do RSpec.describe Dry::Validation::Contract, "Rule#each" do
subject(:contract) { contract_class.new } subject(:contract) { contract_class.new }
context 'using a block' do context "using a block" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
params do params do
@ -20,11 +20,11 @@ RSpec.describe Dry::Validation::Contract, 'Rule#each' do
end end
rule(:nums).each do rule(:nums).each do
key.failure('invalid') if value < 3 key.failure("invalid") if value < 3
end end
rule(hash: :another_nums).each do rule(hash: :another_nums).each do
key.failure('invalid') if value < 3 key.failure("invalid") if value < 3
end end
rule(:nums).each do |context:| rule(:nums).each do |context:|
@ -34,34 +34,34 @@ RSpec.describe Dry::Validation::Contract, 'Rule#each' do
end end
end end
it 'applies rule only when the value is an array' do it "applies rule only when the value is an array" do
expect(contract.(nums: 'oops').errors.to_h).to eql(nums: ['must be an array']) expect(contract.(nums: "oops").errors.to_h).to eql(nums: ["must be an array"])
end end
it 'applies rule when an item passed schema checks' do it "applies rule when an item passed schema checks" do
expect(contract.(nums: ['oops', 1, 4, 0]).errors.to_h) expect(contract.(nums: ["oops", 1, 4, 0]).errors.to_h)
.to eql(nums: { 0 => ['must be an integer'], 1 => ['invalid'], 3 => ['invalid'] }) .to eql(nums: {0 => ["must be an integer"], 1 => ["invalid"], 3 => ["invalid"]})
end end
it 'applies rule to nested values when an item passed schema checks' do it "applies rule to nested values when an item passed schema checks" do
expect(contract.(nums: [4], hash: { another_nums: ['oops', 1, 4] }).errors.to_h) expect(contract.(nums: [4], hash: {another_nums: ["oops", 1, 4]}).errors.to_h)
.to eql(hash: { another_nums: { 0 => ['must be an integer'], 1 => ['invalid'] } }) .to eql(hash: {another_nums: {0 => ["must be an integer"], 1 => ["invalid"]}})
end end
it 'passes block options' do it "passes block options" do
expect(contract.(nums: [10, 20]).context[:sum]).to eql(30) expect(contract.(nums: [10, 20]).context[:sum]).to eql(30)
end end
end end
context 'using a simple macro' do context "using a simple macro" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:even?) do register_macro(:even?) do
key.failure('invalid') unless value.even? key.failure("invalid") unless value.even?
end end
params do params do
@ -72,34 +72,34 @@ RSpec.describe Dry::Validation::Contract, 'Rule#each' do
end end
rule(:nums).each(:even?) rule(:nums).each(:even?)
rule('hash.another_nums').each(:even?) rule("hash.another_nums").each(:even?)
end end
end end
it 'applies rule when an item passed schema checks' do it "applies rule when an item passed schema checks" do
expect(contract.(nums: [2, 3]).errors.to_h) expect(contract.(nums: [2, 3]).errors.to_h)
.to eql(nums: { 1 => ['invalid'] }) .to eql(nums: {1 => ["invalid"]})
end end
it 'applies rule to nested values when an item passed schema checks' do it "applies rule to nested values when an item passed schema checks" do
expect(contract.(nums: [4], hash: { another_nums: [2, 3] }).errors.to_h) expect(contract.(nums: [4], hash: {another_nums: [2, 3]}).errors.to_h)
.to eql(hash: { another_nums: { 1 => ['invalid'] } }) .to eql(hash: {another_nums: {1 => ["invalid"]}})
end end
end end
context 'using multiple macros' do context "using multiple macros" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:even?) do register_macro(:even?) do
key.failure('invalid') unless value.even? key.failure("invalid") unless value.even?
end end
register_macro(:below_ten?) do register_macro(:below_ten?) do
key.failure('too big') unless value < 10 key.failure("too big") unless value < 10
end end
params do params do
@ -114,27 +114,27 @@ RSpec.describe Dry::Validation::Contract, 'Rule#each' do
end end
end end
it 'applies rules when an item passed schema checks' do it "applies rules when an item passed schema checks" do
expect(contract.(nums: [2, 15]).errors.to_h) expect(contract.(nums: [2, 15]).errors.to_h)
.to eql(nums: { 1 => ['invalid', 'too big'] }) .to eql(nums: {1 => ["invalid", "too big"]})
end end
it 'applies rules for nested values when an item passed schema checks' do it "applies rules for nested values when an item passed schema checks" do
expect(contract.(nums: [2], hash: { another_nums: [2, 15] }).errors.to_h) expect(contract.(nums: [2], hash: {another_nums: [2, 15]}).errors.to_h)
.to eql(hash: { another_nums: { 1 => ['invalid', 'too big'] } }) .to eql(hash: {another_nums: {1 => ["invalid", "too big"]}})
end end
end end
context 'using a macro with args' do context "using a macro with args" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:min) do |macro:| register_macro(:min) do |macro:|
min = macro.args[0] min = macro.args[0]
key.failure('invalid') if value < min key.failure("invalid") if value < min
end end
params do params do
@ -149,27 +149,27 @@ RSpec.describe Dry::Validation::Contract, 'Rule#each' do
end end
end end
it 'applies rule when an item passed schema checks' do it "applies rule when an item passed schema checks" do
expect(contract.(nums: ['oops', 1, 4, 0]).errors.to_h) expect(contract.(nums: ["oops", 1, 4, 0]).errors.to_h)
.to eql(nums: { 0 => ['must be an integer'], 1 => ['invalid'], 3 => ['invalid'] }) .to eql(nums: {0 => ["must be an integer"], 1 => ["invalid"], 3 => ["invalid"]})
end end
it 'applies rule to nested values when an item passed schema checks' do it "applies rule to nested values when an item passed schema checks" do
expect(contract.(nums: [4], hash: { another_nums: ['oops', 1, 4] }).errors.to_h) expect(contract.(nums: [4], hash: {another_nums: ["oops", 1, 4]}).errors.to_h)
.to eql(hash: { another_nums: { 0 => ['must be an integer'], 1 => ['invalid'] } }) .to eql(hash: {another_nums: {0 => ["must be an integer"], 1 => ["invalid"]}})
end end
end end
context 'using a macro with multiple args' do context "using a macro with multiple args" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:between) do |macro:| register_macro(:between) do |macro:|
min, max = macro.args[0..1] min, max = macro.args[0..1]
key.failure('invalid') unless (min..max).cover?(value) key.failure("invalid") unless (min..max).cover?(value)
end end
params do params do
@ -184,32 +184,32 @@ RSpec.describe Dry::Validation::Contract, 'Rule#each' do
end end
end end
it 'applies rule when an item passed schema checks' do it "applies rule when an item passed schema checks" do
expect(contract.(nums: ['oops', 4, 0, 6]).errors.to_h) expect(contract.(nums: ["oops", 4, 0, 6]).errors.to_h)
.to eql(nums: { 0 => ['must be an integer'], 2 => ['invalid'], 3 => ['invalid'] }) .to eql(nums: {0 => ["must be an integer"], 2 => ["invalid"], 3 => ["invalid"]})
end end
it 'applies rule with nested values when an item passed schema checks' do it "applies rule with nested values when an item passed schema checks" do
expect(contract.(nums: [4], hash: { another_nums: ['oops', 4, 0] }).errors.to_h) expect(contract.(nums: [4], hash: {another_nums: ["oops", 4, 0]}).errors.to_h)
.to eql(hash: { another_nums: { 0 => ['must be an integer'], 2 => ['invalid'] } }) .to eql(hash: {another_nums: {0 => ["must be an integer"], 2 => ["invalid"]}})
end end
end end
context 'using multiple macros with args' do context "using multiple macros with args" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:min) do |macro:| register_macro(:min) do |macro:|
min = macro.args[0] min = macro.args[0]
key.failure('invalid') if value < min key.failure("invalid") if value < min
end end
register_macro(:max) do |macro:| register_macro(:max) do |macro:|
max = macro.args[0] max = macro.args[0]
key.failure('invalid') if value > max key.failure("invalid") if value > max
end end
params do params do
@ -224,14 +224,14 @@ RSpec.describe Dry::Validation::Contract, 'Rule#each' do
end end
end end
it 'applies rules when an item passed schema checks' do it "applies rules when an item passed schema checks" do
expect(contract.(nums: ['oops', 4, 0, 6]).errors.to_h) expect(contract.(nums: ["oops", 4, 0, 6]).errors.to_h)
.to eql(nums: { 0 => ['must be an integer'], 2 => ['invalid'], 3 => ['invalid'] }) .to eql(nums: {0 => ["must be an integer"], 2 => ["invalid"], 3 => ["invalid"]})
end end
it 'applies rules for nested values when an item passed schema checks' do it "applies rules for nested values when an item passed schema checks" do
expect(contract.(nums: [4], hash: { another_nums: ['oops', 4, 0] }).errors.to_h) expect(contract.(nums: [4], hash: {another_nums: ["oops", 4, 0]}).errors.to_h)
.to eql(hash: { another_nums: { 0 => ['must be an integer'], 2 => ['invalid'] } }) .to eql(hash: {another_nums: {0 => ["must be an integer"], 2 => ["invalid"]}})
end end
end end
end end

View File

@ -1,11 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, '.rule' do RSpec.describe Dry::Validation::Contract, ".rule" do
subject(:contract) { contract_class.new } subject(:contract) { contract_class.new }
context 'with a nested hash' do context "with a nested hash" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
params do params do
@ -18,30 +18,30 @@ RSpec.describe Dry::Validation::Contract, '.rule' do
end end
rule(:email) do rule(:email) do
key.failure('invalid email') unless value.include?('@') key.failure("invalid email") unless value.include?("@")
end end
rule('address.zipcode') do rule("address.zipcode") do
key.failure('bad format') unless value.include?('-') key.failure("bad format") unless value.include?("-")
end end
end end
end end
context 'when nested values fail both schema and rule checks' do context "when nested values fail both schema and rule checks" do
it 'produces schema and rule errors' do it "produces schema and rule errors" do
expect(contract.(email: 'jane@doe.org', address: { city: 'NYC', zipcode: '123' }).errors.to_h) expect(contract.(email: "jane@doe.org", address: {city: "NYC", zipcode: "123"}).errors.to_h)
.to eql(address: { street: ['is missing'], zipcode: ['bad format'] }) .to eql(address: {street: ["is missing"], zipcode: ["bad format"]})
end end
end end
context 'when empty hash is provided' do context "when empty hash is provided" do
it 'produces missing-key errors' do it "produces missing-key errors" do
expect(contract.({}).errors.to_h).to eql(email: ['is missing'], address: ['is missing']) expect(contract.({}).errors.to_h).to eql(email: ["is missing"], address: ["is missing"])
end end
end end
end end
context 'with a rule that depends on two nested values' do context "with a rule that depends on two nested values" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
params do params do
@ -52,19 +52,19 @@ RSpec.describe Dry::Validation::Contract, '.rule' do
end end
rule(event: %i[active_from active_until]) do rule(event: %i[active_from active_until]) do
key.failure('invalid dates') if value[0] < value[1] key.failure("invalid dates") if value[0] < value[1]
end end
end end
end end
it 'does not execute rule when the schema checks failed' do it "does not execute rule when the schema checks failed" do
result = contract.(event: { active_from: Date.today, active_until: nil }) result = contract.(event: {active_from: Date.today, active_until: nil})
expect(result.errors.to_h).to eql(event: { active_until: ['must be a date'] }) expect(result.errors.to_h).to eql(event: {active_until: ["must be a date"]})
end end
end end
context 'with a nested array' do context "with a nested array" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
params do params do
@ -73,21 +73,21 @@ RSpec.describe Dry::Validation::Contract, '.rule' do
end end
end end
rule('address.phones').each do rule("address.phones").each do
key.failure('invalid phone') unless value.start_with?('+48') key.failure("invalid phone") unless value.start_with?("+48")
end end
end end
end end
context 'when one of the values fails' do context "when one of the values fails" do
it 'produces an error for the invalid value' do it "produces an error for the invalid value" do
expect(contract.(address: { phones: ['+48123', '+47412', nil] }).errors.to_h) expect(contract.(address: {phones: ["+48123", "+47412", nil]}).errors.to_h)
.to eql(address: { phones: { 1 => ['invalid phone'], 2 => ['must be a string'] } }) .to eql(address: {phones: {1 => ["invalid phone"], 2 => ["must be a string"]}})
end end
end end
context 'with a path intersection' do context "with a path intersection" do
context 'when the longest path is a first' do context "when the longest path is a first" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
params do params do
@ -97,19 +97,19 @@ RSpec.describe Dry::Validation::Contract, '.rule' do
end end
rule(:addresses).each do rule(:addresses).each do
key(path.keys + [:phone]).failure('invalid phone') key(path.keys + [:phone]).failure("invalid phone")
key.failure('invalid list') key.failure("invalid list")
end end
end end
it 'produces an error for all paths' do it "produces an error for all paths" do
expect(contract.(addresses: [{ phone: '+48123' }]).errors.to_h) expect(contract.(addresses: [{phone: "+48123"}]).errors.to_h)
.to eql(addresses: { 0 => [['invalid list'], [{ phone: 'invalid phone' }]] }) .to eql(addresses: {0 => [["invalid list"], [{phone: "invalid phone"}]]})
end end
end end
end end
context 'when the longest path is a last' do context "when the longest path is a last" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
params do params do
@ -119,14 +119,14 @@ RSpec.describe Dry::Validation::Contract, '.rule' do
end end
rule(:addresses).each do rule(:addresses).each do
key.failure('invalid list') key.failure("invalid list")
key(path.keys + [:phone]).failure('invalid phone') key(path.keys + [:phone]).failure("invalid phone")
end end
end end
it 'produces an error for all paths' do it "produces an error for all paths" do
expect(contract.(addresses: [{ phone: '+48123' }]).errors.to_h) expect(contract.(addresses: [{phone: "+48123"}]).errors.to_h)
.to eql(addresses: { 0 => [['invalid list'], [{ phone: 'invalid phone' }]] }) .to eql(addresses: {0 => [["invalid list"], [{phone: "invalid phone"}]]})
end end
end end
end end

View File

@ -1,15 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, 'Rule#validate' do RSpec.describe Dry::Validation::Contract, "Rule#validate" do
subject(:contract) { contract_class.new } subject(:contract) { contract_class.new }
context 'using a block' do context "using a block" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
params do params do
@ -17,26 +17,26 @@ RSpec.describe Dry::Validation::Contract, 'Rule#validate' do
end end
rule(:num).validate do rule(:num).validate do
key.failure('invalid') if value < 3 key.failure("invalid") if value < 3
end end
end end
end end
it 'applies rule when an item passed schema checks' do it "applies rule when an item passed schema checks" do
expect(contract.(num: 2).errors.to_h) expect(contract.(num: 2).errors.to_h)
.to eql(num: ['invalid']) .to eql(num: ["invalid"])
end end
end end
context 'using a simple macro' do context "using a simple macro" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:even?) do register_macro(:even?) do
key.failure('invalid') unless value.even? key.failure("invalid") unless value.even?
end end
params do params do
@ -47,25 +47,25 @@ RSpec.describe Dry::Validation::Contract, 'Rule#validate' do
end end
end end
it 'applies first rule when an item passed schema checks' do it "applies first rule when an item passed schema checks" do
expect(contract.(num: 3).errors.to_h) expect(contract.(num: 3).errors.to_h)
.to eql(num: ['invalid']) .to eql(num: ["invalid"])
end end
end end
context 'using multiple macros' do context "using multiple macros" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:even?) do register_macro(:even?) do
key.failure('invalid') unless value.even? key.failure("invalid") unless value.even?
end end
register_macro(:below_ten?) do register_macro(:below_ten?) do
key.failure('too big') unless value < 10 key.failure("too big") unless value < 10
end end
params do params do
@ -76,22 +76,22 @@ RSpec.describe Dry::Validation::Contract, 'Rule#validate' do
end end
end end
it 'applies rules when an item passed schema checks' do it "applies rules when an item passed schema checks" do
expect(contract.(num: 15).errors.to_h) expect(contract.(num: 15).errors.to_h)
.to eql(num: ['invalid', 'too big']) .to eql(num: ["invalid", "too big"])
end end
end end
context 'using a macro with args' do context "using a macro with args" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:min) do |macro:| register_macro(:min) do |macro:|
min = macro.args[0] min = macro.args[0]
key.failure('invalid') if value < min key.failure("invalid") if value < min
end end
params do params do
@ -102,22 +102,22 @@ RSpec.describe Dry::Validation::Contract, 'Rule#validate' do
end end
end end
it 'applies rule when an item passed schema checks' do it "applies rule when an item passed schema checks" do
expect(contract.(num: 2).errors.to_h) expect(contract.(num: 2).errors.to_h)
.to eql(num: ['invalid']) .to eql(num: ["invalid"])
end end
end end
context 'using a macro with multiple args' do context "using a macro with multiple args" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:between) do |macro:| register_macro(:between) do |macro:|
min, max = macro.args[0..1] min, max = macro.args[0..1]
key.failure('invalid') unless (min..max).cover?(value) key.failure("invalid") unless (min..max).cover?(value)
end end
params do params do
@ -128,27 +128,27 @@ RSpec.describe Dry::Validation::Contract, 'Rule#validate' do
end end
end end
it 'applies rule when an item passed schema checks' do it "applies rule when an item passed schema checks" do
expect(contract.(num: 2).errors.to_h) expect(contract.(num: 2).errors.to_h)
.to eql(num: ['invalid']) .to eql(num: ["invalid"])
end end
end end
context 'using multiple macros with args' do context "using multiple macros with args" do
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
register_macro(:min) do |macro:| register_macro(:min) do |macro:|
min = macro.args[0] min = macro.args[0]
key.failure('too small') if value < min key.failure("too small") if value < min
end end
register_macro(:max) do |macro:| register_macro(:max) do |macro:|
max = macro.args[0] max = macro.args[0]
key.failure('too big') if value > max key.failure("too big") if value > max
end end
params do params do
@ -159,14 +159,14 @@ RSpec.describe Dry::Validation::Contract, 'Rule#validate' do
end end
end end
it 'applies first rule when an item passed schema checks' do it "applies first rule when an item passed schema checks" do
expect(contract.(num: 2).errors.to_h) expect(contract.(num: 2).errors.to_h)
.to eql(num: ['too small']) .to eql(num: ["too small"])
end end
it 'applies second rule when an item passed schema checks' do it "applies second rule when an item passed schema checks" do
expect(contract.(num: 6).errors.to_h) expect(contract.(num: 6).errors.to_h)
.to eql(num: ['too big']) .to eql(num: ["too big"])
end end
end end
end end

View File

@ -1,14 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, '.rule' do RSpec.describe Dry::Validation::Contract, ".rule" do
subject(:contract) { contract_class.new } subject(:contract) { contract_class.new }
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
def self.name def self.name
'TestContract' "TestContract"
end end
params do params do
@ -24,154 +24,154 @@ RSpec.describe Dry::Validation::Contract, '.rule' do
end end
end end
context 'when the name matches one of the keys' do context "when the name matches one of the keys" do
before do before do
contract_class.rule(:login) do contract_class.rule(:login) do
key.failure('is too short') if values[:login].size < 3 key.failure("is too short") if values[:login].size < 3
end end
end end
it 'applies rule when value passed schema checks' do it "applies rule when value passed schema checks" do
expect(contract.(email: 'jane@doe.org', login: 'ab').errors.to_h) expect(contract.(email: "jane@doe.org", login: "ab").errors.to_h)
.to eql(login: ['is too short']) .to eql(login: ["is too short"])
end end
end end
context 'when the name does not match one of the keys' do context "when the name does not match one of the keys" do
before do before do
contract_class.rule do contract_class.rule do
key(:custom).failure('this works') key(:custom).failure("this works")
end end
end end
it 'applies the rule regardless of the schema result' do it "applies the rule regardless of the schema result" do
expect(contract.(email: 'jane@doe.org', login: 'jane').errors.to_h) expect(contract.(email: "jane@doe.org", login: "jane").errors.to_h)
.to eql(custom: ['this works']) .to eql(custom: ["this works"])
end end
end end
context 'with a hash as the key identifier' do context "with a hash as the key identifier" do
before do before do
contract_class.rule(details: { address: :street }) do contract_class.rule(details: {address: :street}) do
key.failure('cannot be empty') if values[:details][:address][:street].strip.empty? key.failure("cannot be empty") if values[:details][:address][:street].strip.empty?
end end
end end
it 'applies the rule when nested value passed schema checks' do it "applies the rule when nested value passed schema checks" do
expect(contract.(email: 'jane@doe.org', login: 'jane', details: nil).errors.to_h) expect(contract.(email: "jane@doe.org", login: "jane", details: nil).errors.to_h)
.to eql(details: ['must be a hash']) .to eql(details: ["must be a hash"])
expect(contract.(email: 'jane@doe.org', login: 'jane', details: { address: nil }).errors.to_h) expect(contract.(email: "jane@doe.org", login: "jane", details: {address: nil}).errors.to_h)
.to eql(details: { address: ['must be a hash'] }) .to eql(details: {address: ["must be a hash"]})
expect(contract.(email: 'jane@doe.org', login: 'jane', details: { address: { street: ' ' } }).errors.to_h) expect(contract.(email: "jane@doe.org", login: "jane", details: {address: {street: " "}}).errors.to_h)
.to eql(details: { address: { street: ['cannot be empty'] } }) .to eql(details: {address: {street: ["cannot be empty"]}})
end end
end end
context 'with a rule for nested hash and another rule for its member' do context "with a rule for nested hash and another rule for its member" do
before do before do
contract_class.rule(details: :address) do contract_class.rule(details: :address) do
key.failure('invalid no matter what') key.failure("invalid no matter what")
end end
contract_class.rule(details: :address) do contract_class.rule(details: :address) do
key.failure('seriously invalid') key.failure("seriously invalid")
end end
contract_class.rule(details: { address: :street }) do contract_class.rule(details: {address: :street}) do
key.failure('cannot be empty') if values[:details][:address][:street].strip.empty? key.failure("cannot be empty") if values[:details][:address][:street].strip.empty?
end end
contract_class.rule(details: { address: :street }) do contract_class.rule(details: {address: :street}) do
key.failure('must include a number') unless values[:details][:address][:street].match?(/\d+/) key.failure("must include a number") unless values[:details][:address][:street].match?(/\d+/)
end end
end end
it 'applies the rule when nested value passed schema checks' do it "applies the rule when nested value passed schema checks" do
expect(contract.(email: 'jane@doe.org', login: 'jane', details: { address: { street: ' ' } }).errors.to_h) expect(contract.(email: "jane@doe.org", login: "jane", details: {address: {street: " "}}).errors.to_h)
.to eql( .to eql(
details: { address: [ details: {address: [
['invalid no matter what', 'seriously invalid'], ["invalid no matter what", "seriously invalid"],
{ street: ['cannot be empty', 'must include a number'] } {street: ["cannot be empty", "must include a number"]}
] } ]}
) )
end end
end end
context 'with a rule that sets a general base error for the whole input' do context "with a rule that sets a general base error for the whole input" do
before do before do
contract_class.rule do contract_class.rule do
key.failure('this whole thing is invalid') key.failure("this whole thing is invalid")
end end
end end
it 'sets a base error not attached to any key' do it "sets a base error not attached to any key" do
expect(contract.(email: 'jane@doe.org', login: '').errors.to_h) expect(contract.(email: "jane@doe.org", login: "").errors.to_h)
.to eql(login: ['must be filled'], nil => ['this whole thing is invalid']) .to eql(login: ["must be filled"], nil => ["this whole thing is invalid"])
expect(contract.(email: 'jane@doe.org', login: '').errors.filter(:base?).map(&:to_s)) expect(contract.(email: "jane@doe.org", login: "").errors.filter(:base?).map(&:to_s))
.to eql(['this whole thing is invalid']) .to eql(["this whole thing is invalid"])
end end
end end
context 'with a list of keys' do context "with a list of keys" do
before do before do
contract_class.rule(:email, :login) do contract_class.rule(:email, :login) do
if !values[:email].empty? && !values[:login].empty? if !values[:email].empty? && !values[:login].empty?
key(:login).failure('is not needed when email is provided') key(:login).failure("is not needed when email is provided")
end end
end end
end end
it 'applies the rule when all values passed schema checks' do it "applies the rule when all values passed schema checks" do
expect(contract.(email: nil, login: nil).errors.to_h) expect(contract.(email: nil, login: nil).errors.to_h)
.to eql(email: ['must be filled'], login: ['must be filled']) .to eql(email: ["must be filled"], login: ["must be filled"])
expect(contract.(email: 'jane@doe.org', login: 'jane').errors.to_h) expect(contract.(email: "jane@doe.org", login: "jane").errors.to_h)
.to eql(login: ['is not needed when email is provided']) .to eql(login: ["is not needed when email is provided"])
end end
end end
context 'when keys are missing in the schema' do context "when keys are missing in the schema" do
it 'raises error with a list of symbol keys' do it "raises error with a list of symbol keys" do
expect { contract_class.rule(:invalid, :wrong) } expect { contract_class.rule(:invalid, :wrong) }
.to raise_error( .to raise_error(
Dry::Validation::InvalidKeysError, Dry::Validation::InvalidKeysError,
'TestContract.rule specifies keys that are not defined by the schema: [:invalid, :wrong]' "TestContract.rule specifies keys that are not defined by the schema: [:invalid, :wrong]"
) )
end end
it 'raises error with a hash path' do it "raises error with a hash path" do
expect { contract_class.rule(invalid: :wrong) } expect { contract_class.rule(invalid: :wrong) }
.to raise_error( .to raise_error(
Dry::Validation::InvalidKeysError, Dry::Validation::InvalidKeysError,
'TestContract.rule specifies keys that are not defined by the schema: [{:invalid=>:wrong}]' "TestContract.rule specifies keys that are not defined by the schema: [{:invalid=>:wrong}]"
) )
end end
it 'raises error with a dot notation' do it "raises error with a dot notation" do
expect { contract_class.rule('invalid.wrong') } expect { contract_class.rule("invalid.wrong") }
.to raise_error( .to raise_error(
Dry::Validation::InvalidKeysError, Dry::Validation::InvalidKeysError,
'TestContract.rule specifies keys that are not defined by the schema: ["invalid.wrong"]' 'TestContract.rule specifies keys that are not defined by the schema: ["invalid.wrong"]'
) )
end end
it 'raises error with a hash path with multiple nested keys' do it "raises error with a hash path with multiple nested keys" do
expect { contract_class.rule(invalid: %i[wrong not_here]) } expect { contract_class.rule(invalid: %i[wrong not_here]) }
.to raise_error( .to raise_error(
Dry::Validation::InvalidKeysError, Dry::Validation::InvalidKeysError,
'TestContract.rule specifies keys that are not defined by the schema: [[:invalid, :wrong], [:invalid, :not_here]]' "TestContract.rule specifies keys that are not defined by the schema: [[:invalid, :wrong], [:invalid, :not_here]]"
) )
end end
end end
describe 'abstract contract' do describe "abstract contract" do
let(:abstract_contract) do let(:abstract_contract) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
rule do rule do
base.failure('error from abstract contract') base.failure("error from abstract contract")
end end
end end
end end
@ -184,9 +184,9 @@ RSpec.describe Dry::Validation::Contract, '.rule' do
end end
end end
it 'applies rules from the parent abstract contract' do it "applies rules from the parent abstract contract" do
expect(contract.(name: '').errors.to_h).to eql( expect(contract.(name: "").errors.to_h).to eql(
nil => ['error from abstract contract'], name: ['must be filled'] nil => ["error from abstract contract"], name: ["must be filled"]
) )
end end
end end

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, '.schema' do RSpec.describe Dry::Validation::Contract, ".schema" do
context 'defining a schema via block' do context "defining a schema via block" do
subject(:contract_class) do subject(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
schema do schema do
@ -12,16 +12,16 @@ RSpec.describe Dry::Validation::Contract, '.schema' do
end end
end end
it 'defines a schema' do it "defines a schema" do
expect(contract_class.schema).to be_a(Dry::Schema::Processor) expect(contract_class.schema).to be_a(Dry::Schema::Processor)
end end
it 'returns nil if schema is not defined' do it "returns nil if schema is not defined" do
contract_class = Class.new(Dry::Validation::Contract) contract_class = Class.new(Dry::Validation::Contract)
expect(contract_class.schema).to be(nil) expect(contract_class.schema).to be(nil)
end end
it 'raises an error if schema is already defined' do it "raises an error if schema is already defined" do
expect do expect do
contract_class.schema do contract_class.schema do
required(:login).filled(:string) required(:login).filled(:string)
@ -30,7 +30,7 @@ RSpec.describe Dry::Validation::Contract, '.schema' do
end end
end end
context 'setting an external schema' do context "setting an external schema" do
subject(:contract_class) do subject(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
schema(Test::UserSchema) do schema(Test::UserSchema) do
@ -45,30 +45,30 @@ RSpec.describe Dry::Validation::Contract, '.schema' do
end end
end end
it 'defines a schema' do it "defines a schema" do
expect(contract_class.schema).to be_a(Dry::Schema::Processor) expect(contract_class.schema).to be_a(Dry::Schema::Processor)
end end
it 'extends the schema' do it "extends the schema" do
contract = contract_class.new contract = contract_class.new
expect(contract.(email: '', name: '').errors.to_h) expect(contract.(email: "", name: "").errors.to_h)
.to eql(email: ['must be filled'], name: ['must be filled']) .to eql(email: ["must be filled"], name: ["must be filled"])
end end
context 'schema without block argument' do context "schema without block argument" do
subject(:contract_class) do subject(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
schema Test::UserSchema schema Test::UserSchema
end end
end end
it 'uses the external schema' do it "uses the external schema" do
expect(contract_class.schema).to be_a(Dry::Schema::Processor) expect(contract_class.schema).to be_a(Dry::Schema::Processor)
end end
end end
context 'setting multiple external schemas' do context "setting multiple external schemas" do
subject(:contract_class) do subject(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
schema(Test::UserSchema, Test::CompanySchema) do schema(Test::UserSchema, Test::CompanySchema) do
@ -83,12 +83,12 @@ RSpec.describe Dry::Validation::Contract, '.schema' do
end end
end end
it 'extends the schemas' do it "extends the schemas" do
contract = contract_class.new contract = contract_class.new
expect(contract.(email: '', name: '', company: '').errors.to_h) expect(contract.(email: "", name: "", company: "").errors.to_h)
.to eql(email: ['must be filled'], .to eql(email: ["must be filled"],
name: ['must be filled'], name: ["must be filled"],
company: ['must be filled']) company: ["must be filled"])
end end
end end
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Dry::Validation::Evaluator do RSpec.describe Dry::Validation::Evaluator do
describe '#schema_error?' do describe "#schema_error?" do
let(:contract) do let(:contract) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
schema do schema do
@ -10,20 +10,20 @@ RSpec.describe Dry::Validation::Evaluator do
end end
rule(:name) do rule(:name) do
key.failure('first introduce a valid email') if schema_error?(:email) key.failure("first introduce a valid email") if schema_error?(:email)
end end
end end
end end
it 'checks for errors in given key' do it "checks for errors in given key" do
expect(contract.new.(email: nil, name: 'foo').errors.to_h).to eql( expect(contract.new.(email: nil, name: "foo").errors.to_h).to eql(
email: ['must be a string'], email: ["must be a string"],
name: ['first introduce a valid email'] name: ["first introduce a valid email"]
) )
end end
end end
describe '#rule_error?' do describe "#rule_error?" do
let(:contract) do let(:contract) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
schema do schema do
@ -31,15 +31,15 @@ RSpec.describe Dry::Validation::Evaluator do
end end
rule(:foo) do rule(:foo) do
key.failure('failure added') key.failure("failure added")
key.failure('failure added after checking') if rule_error? key.failure("failure added after checking") if rule_error?
end end
end end
end end
it 'checks for errors in current rule' do it "checks for errors in current rule" do
expect(contract.new.(foo: 'some@email.com').errors.to_h).to eql( expect(contract.new.(foo: "some@email.com").errors.to_h).to eql(
foo: ['failure added', 'failure added after checking'] foo: ["failure added", "failure added after checking"]
) )
end end
end end

View File

@ -13,109 +13,109 @@ RSpec.describe Dry::Validation::Evaluator do
end end
end end
context 'setting key failures using default rule path' do context "setting key failures using default rule path" do
before do before do
contract_class.rule(:email) do contract_class.rule(:email) do
key.failure('is invalid') key.failure("is invalid")
end end
end end
it 'sets error under specified key' do it "sets error under specified key" do
expect(contract.(email: 'foo').errors.to_h).to eql(email: ['is invalid']) expect(contract.(email: "foo").errors.to_h).to eql(email: ["is invalid"])
end end
end end
context 'setting key failures using via explicit path' do context "setting key failures using via explicit path" do
context 'with a string message' do context "with a string message" do
before do before do
contract_class.rule(:email) do contract_class.rule(:email) do
key(:contact).failure('is invalid') key(:contact).failure("is invalid")
end end
end end
it 'sets error under specified key' do it "sets error under specified key" do
expect(contract.(email: 'foo').errors.to_h).to eql(contact: ['is invalid']) expect(contract.(email: "foo").errors.to_h).to eql(contact: ["is invalid"])
end end
end end
context 'with a nested key as a hash and a string message' do context "with a nested key as a hash and a string message" do
before do before do
contract_class.rule(:email) do contract_class.rule(:email) do
key(contact: :details).failure('is invalid') key(contact: :details).failure("is invalid")
end end
end end
it 'sets error under specified key' do it "sets error under specified key" do
expect(contract.(email: 'foo').errors.to_h).to eql(contact: { details: ['is invalid'] }) expect(contract.(email: "foo").errors.to_h).to eql(contact: {details: ["is invalid"]})
end end
end end
context 'with a symbol' do context "with a symbol" do
before do before do
contract_class.config.messages.load_paths << SPEC_ROOT contract_class.config.messages.load_paths << SPEC_ROOT
.join('fixtures/messages/errors.en.yml').realpath .join("fixtures/messages/errors.en.yml").realpath
contract_class.rule(:email) do contract_class.rule(:email) do
key(:contact).failure(:wrong) key(:contact).failure(:wrong)
end end
end end
it 'sets error under specified key' do it "sets error under specified key" do
expect(contract.(email: 'foo').errors.to_h).to eql(contact: ['not right']) expect(contract.(email: "foo").errors.to_h).to eql(contact: ["not right"])
end end
end end
end end
context 'setting base failures' do context "setting base failures" do
before do before do
contract_class.rule(:email) do contract_class.rule(:email) do
base.failure('is invalid') base.failure("is invalid")
end end
end end
it 'sets error under specified key' do it "sets error under specified key" do
expect(contract.(email: 'foo').errors.to_h).to eql(nil => ['is invalid']) expect(contract.(email: "foo").errors.to_h).to eql(nil => ["is invalid"])
end end
end end
context 'setting failures with meta data' do context "setting failures with meta data" do
before do before do
contract_class.rule(:email) do contract_class.rule(:email) do
key.failure(text: 'is invalid', code: 102) key.failure(text: "is invalid", code: 102)
end end
end end
it 'sets error under specified key' do it "sets error under specified key" do
errors = contract.(email: 'foo').errors errors = contract.(email: "foo").errors
expect(errors.to_h).to eql(email: [text: 'is invalid', code: 102]) expect(errors.to_h).to eql(email: [text: "is invalid", code: 102])
expect(errors.first.meta).to eql(code: 102) expect(errors.first.meta).to eql(code: 102)
end end
context 'without :text key' do context "without :text key" do
before do before do
contract_class.rule(:email) do contract_class.rule(:email) do
key.failure(code: 102) key.failure(code: 102)
end end
end end
it 'raises argument error if no text key provided' do it "raises argument error if no text key provided" do
expect { expect {
contract.(email: 'foo').errors contract.(email: "foo").errors
}.to raise_error(ArgumentError, /Hash must contain :text key/) }.to raise_error(ArgumentError, /Hash must contain :text key/)
end end
end end
end end
context 'when localized message id is invalid' do context "when localized message id is invalid" do
before do before do
contract_class.rule(:email) do contract_class.rule(:email) do
key.failure([:oops_bad_id]) key.failure([:oops_bad_id])
end end
end end
it 'raises a meaningful error' do it "raises a meaningful error" do
expect { contract.(email: 'foo') }.to raise_error(ArgumentError, /oops_bad_id/) expect { contract.(email: "foo") }.to raise_error(ArgumentError, /oops_bad_id/)
end end
end end
end end

View File

@ -1,11 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Dry::Validation::Evaluator, 'using context' do RSpec.describe Dry::Validation::Evaluator, "using context" do
before(:all) do before(:all) do
Dry::Validation.load_extensions(:hints) Dry::Validation.load_extensions(:hints)
end end
context 'when key does not exist' do context "when key does not exist" do
subject(:contract) do subject(:contract) do
Dry::Validation.Contract do Dry::Validation.Contract do
schema do schema do
@ -15,25 +15,25 @@ RSpec.describe Dry::Validation::Evaluator, 'using context' do
rule(:user_id) do |context:| rule(:user_id) do |context:|
if values[:user_id].equal?(312) if values[:user_id].equal?(312)
context[:user] = 'jane' context[:user] = "jane"
else else
key(:user).failure('must be jane') key(:user).failure("must be jane")
end end
end end
rule(:email) do |context:| rule(:email) do |context:|
key.failure('is invalid') if context[:user] == 'jane' && values[:email] != 'jane@doe.org' key.failure("is invalid") if context[:user] == "jane" && values[:email] != "jane@doe.org"
end end
end end
end end
it 'stores new values between rule execution' do it "stores new values between rule execution" do
expect(contract.(user_id: 3, email: 'john@doe.org').errors.to_h).to eql(user: ['must be jane']) expect(contract.(user_id: 3, email: "john@doe.org").errors.to_h).to eql(user: ["must be jane"])
expect(contract.(user_id: 312, email: 'john@doe.org').errors.to_h).to eql(email: ['is invalid']) expect(contract.(user_id: 312, email: "john@doe.org").errors.to_h).to eql(email: ["is invalid"])
end end
it 'exposes context in result' do it "exposes context in result" do
expect(contract.(user_id: 312, email: 'jane@doe.org').context[:user]).to eql('jane') expect(contract.(user_id: 312, email: "jane@doe.org").context[:user]).to eql("jane")
end end
end end
end end

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract, '.option' do RSpec.describe Dry::Validation::Contract, ".option" do
subject(:contract_class) do subject(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
option :db option :db
@ -12,18 +12,18 @@ RSpec.describe Dry::Validation::Contract, '.option' do
end end
rule(:email) do rule(:email) do
key.failure('is taken') unless db.unique?(values[:email]) key.failure("is taken") unless db.unique?(values[:email])
end end
end end
end end
let(:db) { double(:db) } let(:db) { double(:db) }
it 'allows injecting objects to the constructor' do it "allows injecting objects to the constructor" do
expect(db).to receive(:unique?).with('jane@doe.org').and_return(false) expect(db).to receive(:unique?).with("jane@doe.org").and_return(false)
contract = contract_class.new(db: db) contract = contract_class.new(db: db)
expect(contract.(email: 'jane@doe.org').errors.to_h).to eql(email: ['is taken']) expect(contract.(email: "jane@doe.org").errors.to_h).to eql(email: ["is taken"])
end end
end end

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
require 'dry/schema/messages/i18n' require "dry/schema/messages/i18n"
RSpec.describe Dry::Validation::Contract, '.inherited' do RSpec.describe Dry::Validation::Contract, ".inherited" do
subject(:child_class) do subject(:child_class) do
Class.new(parent_class) do Class.new(parent_class) do
params do params do
@ -26,15 +26,15 @@ RSpec.describe Dry::Validation::Contract, '.inherited' do
end end
end end
it 'inherits schema params' do it "inherits schema params" do
expect(child_class.__schema__.key_map.map(&:name).sort).to eql(%w[email name]) expect(child_class.__schema__.key_map.map(&:name).sort).to eql(%w[email name])
end end
it 'inherits rules' do it "inherits rules" do
expect(child_class.rules.map(&:keys).sort).to eql([[:email], [:name]]) expect(child_class.rules.map(&:keys).sort).to eql([[:email], [:name]])
end end
it 'inherits configuration' do it "inherits configuration" do
expect(child_class.config.messages.backend).to eql(parent_class.config.messages.backend) expect(child_class.config.messages.backend).to eql(parent_class.config.messages.backend)
expect(child_class.config.messages.load_paths).to eql(parent_class.config.messages.load_paths) expect(child_class.config.messages.load_paths).to eql(parent_class.config.messages.load_paths)
end end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Dry::Validation::Contract, '.macros' do RSpec.describe Dry::Validation::Contract, ".macros" do
subject!(:contract_class) do subject!(:contract_class) do
Class.new(parent_class) do Class.new(parent_class) do
register_macro(:other_macro) {} register_macro(:other_macro) {}
@ -13,14 +13,14 @@ RSpec.describe Dry::Validation::Contract, '.macros' do
end end
end end
it 'returns macros container inherited from the parent' do it "returns macros container inherited from the parent" do
expect(contract_class.macros.key?(:check_things)).to be(true) expect(contract_class.macros.key?(:check_things)).to be(true)
expect(contract_class.macros.key?(:other_macro)).to be(true) expect(contract_class.macros.key?(:other_macro)).to be(true)
expect(parent_class.macros.key?(:other_macro)).to be(false) expect(parent_class.macros.key?(:other_macro)).to be(false)
end end
it 'does not mutate source macro container' do it "does not mutate source macro container" do
expect(parent_class.superclass.macros.key?(:check_things)).to be(false) expect(parent_class.superclass.macros.key?(:check_things)).to be(false)
end end
end end

View File

@ -1,95 +1,95 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract do RSpec.describe Dry::Validation::Contract do
shared_context 'translated messages' do shared_context "translated messages" do
subject(:contract) do subject(:contract) do
contract_class.new contract_class.new
end end
let(:contract_class) do let(:contract_class) do
Class.new(Dry::Validation::Contract) do Class.new(Dry::Validation::Contract) do
config.messages.load_paths << SPEC_ROOT.join('fixtures/messages/errors.en.yml').realpath config.messages.load_paths << SPEC_ROOT.join("fixtures/messages/errors.en.yml").realpath
params do params do
required(:email).filled(:string, min_size?: 3, max_size?: 100) required(:email).filled(:string, min_size?: 3, max_size?: 100)
end end
rule(:email) do rule(:email) do
key.failure(:invalid) unless value.include?('@') key.failure(:invalid) unless value.include?("@")
key.failure(:taken, values.to_h) if value == 'jane@doe.org' key.failure(:taken, values.to_h) if value == "jane@doe.org"
end end
end end
end end
it 'configures messages for the schema' do it "configures messages for the schema" do
expect(contract.schema.config.messages.load_paths) expect(contract.schema.config.messages.load_paths)
.to eql(contract.class.config.messages.load_paths) .to eql(contract.class.config.messages.load_paths)
end end
describe 'result errors' do describe "result errors" do
it 'supports full: true option for schema errors' do it "supports full: true option for schema errors" do
expect(contract.call(email: '').errors(full: true).map(&:to_s)) expect(contract.call(email: "").errors(full: true).map(&:to_s))
.to eql(['E-mail must be filled']) .to eql(["E-mail must be filled"])
end end
it 'supports full: true option for contract errors' do it "supports full: true option for contract errors" do
expect(contract.call(email: 'jane').errors(full: true).map(&:to_s)) expect(contract.call(email: "jane").errors(full: true).map(&:to_s))
.to eql(['E-mail oh noez bad email']) .to eql(["E-mail oh noez bad email"])
end end
end end
describe 'failure' do describe "failure" do
it 'uses messages for failures' do it "uses messages for failures" do
expect(contract.call(email: 'foo').errors.to_h) expect(contract.call(email: "foo").errors.to_h)
.to eql(email: ['oh noez bad email']) .to eql(email: ["oh noez bad email"])
end end
it 'passes tokens to message templates' do it "passes tokens to message templates" do
expect(contract.call(email: 'jane@doe.org').errors.to_h) expect(contract.call(email: "jane@doe.org").errors.to_h)
.to eql(email: ['looks like jane@doe.org is taken']) .to eql(email: ["looks like jane@doe.org is taken"])
end end
end end
end end
context 'using :yaml messages' do context "using :yaml messages" do
before do before do
contract_class.config.messages.backend = :yaml contract_class.config.messages.backend = :yaml
end end
include_context 'translated messages' include_context "translated messages"
end end
context 'using :i18n messages' do context "using :i18n messages" do
before do before do
I18n.available_locales = %i[en pl] I18n.available_locales = %i[en pl]
contract_class.config.messages.backend = :i18n contract_class.config.messages.backend = :i18n
contract_class.config.messages.load_paths << SPEC_ROOT.join('fixtures/messages/errors.pl.yml').realpath contract_class.config.messages.load_paths << SPEC_ROOT.join("fixtures/messages/errors.pl.yml").realpath
contract contract
end end
include_context 'translated messages' include_context "translated messages"
it 'respects I18n.with_locale' do it "respects I18n.with_locale" do
I18n.with_locale(:pl) do I18n.with_locale(:pl) do
expect(contract.call(email: 'foo').errors.to_h).to eql(email: ['oh nie zły email']) expect(contract.call(email: "foo").errors.to_h).to eql(email: ["oh nie zły email"])
end end
I18n.with_locale(:en) do I18n.with_locale(:en) do
expect(contract.call(email: 'foo').errors.to_h).to eql(email: ['oh noez bad email']) expect(contract.call(email: "foo").errors.to_h).to eql(email: ["oh noez bad email"])
end end
expect(contract.call(email: 'foo').errors(locale: :pl).to_h).to eql(email: ['oh nie zły email']) expect(contract.call(email: "foo").errors(locale: :pl).to_h).to eql(email: ["oh nie zły email"])
expect(contract.call(email: 'foo').errors.to_h).to eql(email: ['oh noez bad email']) expect(contract.call(email: "foo").errors.to_h).to eql(email: ["oh noez bad email"])
end end
end end
it 'parses array tokens as a comma separated list' do it "parses array tokens as a comma separated list" do
contract = Class.new(Dry::Validation::Contract) do contract = Class.new(Dry::Validation::Contract) do
config.messages.load_paths << SPEC_ROOT.join('fixtures/messages/errors.en.yml').realpath config.messages.load_paths << SPEC_ROOT.join("fixtures/messages/errors.en.yml").realpath
params do params do
required(:age).filled(:integer) required(:age).filled(:integer)
@ -101,6 +101,6 @@ RSpec.describe Dry::Validation::Contract do
end end
end end
expect(contract.new.call(age: 4).errors.to_h).to eql(age: ['should be included in 1, 2, 3']) expect(contract.new.call(age: 4).errors.to_h).to eql(age: ["should be included in 1, 2, 3"])
end end
end end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/contract' require "dry/validation/contract"
RSpec.describe Dry::Validation::Contract do RSpec.describe Dry::Validation::Contract do
subject(:contract) do subject(:contract) do
@ -14,27 +14,27 @@ RSpec.describe Dry::Validation::Contract do
end end
rule(:email) do rule(:email) do
key.failure('must be unique') key.failure("must be unique")
end end
end end
end end
describe '#inspect' do describe "#inspect" do
it 'returns a string representation' do it "returns a string representation" do
expect(contract.inspect).to eql( expect(contract.inspect).to eql(
%(#<Test::NewUserContract schema=#<Dry::Schema::Params keys=["email"] rules={:email=>"key?(:email) AND key[email](filled? AND str?)"}> rules=[#<Dry::Validation::Rule keys=[:email]>]>) %(#<Test::NewUserContract schema=#<Dry::Schema::Params keys=["email"] rules={:email=>"key?(:email) AND key[email](filled? AND str?)"}> rules=[#<Dry::Validation::Rule keys=[:email]>]>)
) )
end end
end end
describe '.new' do describe ".new" do
it 'raises error when schema is not defined' do it "raises error when schema is not defined" do
Test::NewUserContract.instance_variable_set('@__schema__', nil) Test::NewUserContract.instance_variable_set("@__schema__", nil)
expect { Test::NewUserContract.new } expect { Test::NewUserContract.new }
.to raise_error( .to raise_error(
Dry::Validation::SchemaMissingError, Dry::Validation::SchemaMissingError,
'Test::NewUserContract cannot be instantiated without a schema defined' "Test::NewUserContract cannot be instantiated without a schema defined"
) )
end end
end end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/evaluator' require "dry/validation/evaluator"
RSpec.describe Dry::Validation::Evaluator do RSpec.describe Dry::Validation::Evaluator do
subject(:evaluator) do subject(:evaluator) do
@ -12,27 +12,27 @@ RSpec.describe Dry::Validation::Evaluator do
end end
let(:options) do let(:options) do
{ keys: [:email], result: {}, values: values, _context: {} } {keys: [:email], result: {}, values: values, _context: {}}
end end
let(:values) do let(:values) do
{} {}
end end
describe 'delegation' do describe "delegation" do
let(:block) do let(:block) do
proc { proc {
key.failure('it works') if works? key.failure("it works") if works?
} }
end end
it 'delegates to the contract' do it "delegates to the contract" do
expect(contract).to receive(:works?).and_return(true) expect(contract).to receive(:works?).and_return(true)
expect(evaluator.failures[0][:path].to_a).to eql([:email]) expect(evaluator.failures[0][:path].to_a).to eql([:email])
expect(evaluator.failures[0][:message]).to eql('it works') expect(evaluator.failures[0][:message]).to eql("it works")
end end
describe 'with custom methods defined on the contract' do describe "with custom methods defined on the contract" do
let(:contract) do let(:contract) do
double(contract: :my_contract) double(contract: :my_contract)
end end
@ -41,8 +41,8 @@ RSpec.describe Dry::Validation::Evaluator do
proc { key.failure("message with #{contract}") } proc { key.failure("message with #{contract}") }
end end
it 'forwards to the contract' do it "forwards to the contract" do
expect(evaluator.failures[0][:message]).to eql('message with my_contract') expect(evaluator.failures[0][:message]).to eql("message with my_contract")
end end
end end
end end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Dry::Validation::Macros, ':acceptance' do RSpec.describe Dry::Validation::Macros, ":acceptance" do
subject(:contract) do subject(:contract) do
Dry::Validation::Contract.build do Dry::Validation::Contract.build do
schema do schema do
@ -11,11 +11,11 @@ RSpec.describe Dry::Validation::Macros, ':acceptance' do
end end
end end
it 'succeeds when value is true' do it "succeeds when value is true" do
expect(contract.(terms: true)).to be_success expect(contract.(terms: true)).to be_success
end end
it 'fails when value is not true' do it "fails when value is not true" do
expect(contract.(terms: false).errors.to_h).to eql(terms: ['must accept terms']) expect(contract.(terms: false).errors.to_h).to eql(terms: ["must accept terms"])
end end
end end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe 'Defining custom macros' do RSpec.describe "Defining custom macros" do
subject(:contract) do subject(:contract) do
contract_class.new contract_class.new
end end
@ -17,49 +17,49 @@ RSpec.describe 'Defining custom macros' do
class Test::BaseContract < Dry::Validation::Contract; end class Test::BaseContract < Dry::Validation::Contract; end
end end
context 'using a macro without options' do context "using a macro without options" do
shared_context 'a contract with a custom macro' do shared_context "a contract with a custom macro" do
before do before do
contract_class.rule(:numbers).validate(:even_numbers) contract_class.rule(:numbers).validate(:even_numbers)
end end
it 'succeeds with valid input' do it "succeeds with valid input" do
expect(contract.(numbers: [2, 4, 6])).to be_success expect(contract.(numbers: [2, 4, 6])).to be_success
end end
it 'fails with invalid input' do it "fails with invalid input" do
expect(contract.(numbers: [1, 2, 3]).errors.to_h).to eql(numbers: ['all numbers must be even']) expect(contract.(numbers: [1, 2, 3]).errors.to_h).to eql(numbers: ["all numbers must be even"])
end end
end end
context 'using macro from the global registry' do context "using macro from the global registry" do
before do before do
Dry::Validation.register_macro(:even_numbers) do Dry::Validation.register_macro(:even_numbers) do
key.failure('all numbers must be even') unless values[key_name].all?(&:even?) key.failure("all numbers must be even") unless values[key_name].all?(&:even?)
end end
end end
after do after do
Dry::Validation::Macros.container._container.delete('even_numbers') Dry::Validation::Macros.container._container.delete("even_numbers")
end end
include_context 'a contract with a custom macro' include_context "a contract with a custom macro"
end end
context 'using macro from contract itself' do context "using macro from contract itself" do
before do before do
Test::BaseContract.register_macro(:even_numbers) do Test::BaseContract.register_macro(:even_numbers) do
key.failure('all numbers must be even') unless values[key_name].all?(&:even?) key.failure("all numbers must be even") unless values[key_name].all?(&:even?)
end end
end end
after do after do
Test::BaseContract.macros._container.delete('even_numbers') Test::BaseContract.macros._container.delete("even_numbers")
end end
end end
end end
context 'using a macro with options' do context "using a macro with options" do
before do before do
Test::BaseContract.register_macro(:min) do |context:, macro:| Test::BaseContract.register_macro(:min) do |context:, macro:|
num = macro.args[0] num = macro.args[0]
@ -71,16 +71,16 @@ RSpec.describe 'Defining custom macros' do
end end
after do after do
Test::BaseContract.macros._container.delete('min') Test::BaseContract.macros._container.delete("min")
end end
it 'fails with invalid input' do it "fails with invalid input" do
expect(contract.(numbers: [1]).errors.to_h) expect(contract.(numbers: [1]).errors.to_h)
.to eql(numbers: ['must have at least 3 items']) .to eql(numbers: ["must have at least 3 items"])
end end
end end
context 'using a macro with a range option' do context "using a macro with a range option" do
before do before do
Test::BaseContract.register_macro(:in_range) do |macro:| Test::BaseContract.register_macro(:in_range) do |macro:|
range = macro.args[0] range = macro.args[0]
@ -93,14 +93,14 @@ RSpec.describe 'Defining custom macros' do
end end
after do after do
Test::BaseContract.macros._container.delete('in_range') Test::BaseContract.macros._container.delete("in_range")
end end
it 'succeeds with valid input' do it "succeeds with valid input" do
expect(contract.(numbers: [1, 2, 3])).to be_success expect(contract.(numbers: [1, 2, 3])).to be_success
end end
it 'fails with invalid input' do it "fails with invalid input" do
expect(contract.(numbers: [1, 2, 6])).to be_failure expect(contract.(numbers: [1, 2, 6])).to be_failure
end end
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Dry::Validation::Messages::Resolver, '#message' do RSpec.describe Dry::Validation::Messages::Resolver, "#message" do
shared_context 'resolving' do shared_context "resolving" do
subject(:resolver) do subject(:resolver) do
contract_class.new(schema: proc {}).message_resolver contract_class.new(schema: proc {}).message_resolver
end end
@ -17,30 +17,30 @@ RSpec.describe Dry::Validation::Messages::Resolver, '#message' do
I18n.available_locales << :pl I18n.available_locales << :pl
end end
context ':en' do context ":en" do
let(:locale) { :en } let(:locale) { :en }
it 'returns message text for base rule' do it "returns message text for base rule" do
expect(resolver.message(:not_weekend, path: [nil], locale: locale)) expect(resolver.message(:not_weekend, path: [nil], locale: locale))
.to eql(['this only works on weekends', {}]) .to eql(["this only works on weekends", {}])
end end
it 'returns message text for flat rule' do it "returns message text for flat rule" do
expect(resolver.message(:taken, path: [:email], tokens: { email: 'jane@doe.org' }, locale: locale)) expect(resolver.message(:taken, path: [:email], tokens: {email: "jane@doe.org"}, locale: locale))
.to eql(['looks like jane@doe.org is taken', {}]) .to eql(["looks like jane@doe.org is taken", {}])
end end
it 'returns message text for nested rule when it is defined under root' do it "returns message text for nested rule when it is defined under root" do
expect(resolver.message(:invalid, path: %i[address city], locale: locale)) expect(resolver.message(:invalid, path: %i[address city], locale: locale))
.to eql(['is not a valid city name', {}]) .to eql(["is not a valid city name", {}])
end end
it 'returns message text for nested rule' do it "returns message text for nested rule" do
expect(resolver.message(:invalid, path: %i[address street], locale: locale)) expect(resolver.message(:invalid, path: %i[address street], locale: locale))
.to eql(["doesn't look good", {}]) .to eql(["doesn't look good", {}])
end end
it 'raises error when template was not found' do it "raises error when template was not found" do
expect { resolver.message(:not_here, path: [:email]) } expect { resolver.message(:not_here, path: [:email]) }
.to raise_error(Dry::Validation::MissingMessageError, <<~STR) .to raise_error(Dry::Validation::MissingMessageError, <<~STR)
Message template for :not_here under "email" was not found Message template for :not_here under "email" was not found
@ -48,44 +48,44 @@ RSpec.describe Dry::Validation::Messages::Resolver, '#message' do
end end
end end
context ':pl' do context ":pl" do
let(:locale) { :pl } let(:locale) { :pl }
it 'returns message text for base rule' do it "returns message text for base rule" do
expect(resolver.message(:not_weekend, path: [nil], locale: locale)) expect(resolver.message(:not_weekend, path: [nil], locale: locale))
.to eql(['to działa tylko w weekendy', {}]) .to eql(["to działa tylko w weekendy", {}])
end end
it 'returns message text for flat rule' do it "returns message text for flat rule" do
expect(resolver.message(:taken, path: [:email], tokens: { email: 'jane@doe.org' }, locale: locale)) expect(resolver.message(:taken, path: [:email], tokens: {email: "jane@doe.org"}, locale: locale))
.to eql(['wygląda, że jane@doe.org jest zajęty', {}]) .to eql(["wygląda, że jane@doe.org jest zajęty", {}])
end end
it 'returns message text for nested rule when it is defined under root' do it "returns message text for nested rule when it is defined under root" do
expect(resolver.message(:invalid, path: %i[address city], locale: locale)) expect(resolver.message(:invalid, path: %i[address city], locale: locale))
.to eql(['nie jest poprawną nazwą miasta', {}]) .to eql(["nie jest poprawną nazwą miasta", {}])
end end
it 'returns message text for nested rule' do it "returns message text for nested rule" do
expect(resolver.message(:invalid, path: %i[address street], locale: locale)) expect(resolver.message(:invalid, path: %i[address street], locale: locale))
.to eql(['nie wygląda dobrze', {}]) .to eql(["nie wygląda dobrze", {}])
end end
end end
end end
context 'using :yaml' do context "using :yaml" do
before do before do
contract_class.config.messages.backend = :yaml contract_class.config.messages.backend = :yaml
end end
include_context 'resolving' include_context "resolving"
end end
context 'using :i18n' do context "using :i18n" do
before do before do
contract_class.config.messages.backend = :i18n contract_class.config.messages.backend = :i18n
end end
include_context 'resolving' include_context "resolving"
end end
end end

View File

@ -1,63 +1,63 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Dry::Validation::Result do RSpec.describe Dry::Validation::Result do
describe '#inspect' do describe "#inspect" do
let(:params) do let(:params) do
double(:params, message_set: [], to_h: { email: 'jane@doe.org' }) double(:params, message_set: [], to_h: {email: "jane@doe.org"})
end end
it 'returns a string representation' do it "returns a string representation" do
result = Dry::Validation::Result.new(params) do |r| result = Dry::Validation::Result.new(params) do |r|
r.add_error(Dry::Validation::Message.new('not valid', path: :email)) r.add_error(Dry::Validation::Message.new("not valid", path: :email))
end end
expect(result.inspect).to eql('#<Dry::Validation::Result{:email=>"jane@doe.org"} errors={:email=>["not valid"]}>') expect(result.inspect).to eql('#<Dry::Validation::Result{:email=>"jane@doe.org"} errors={:email=>["not valid"]}>')
end end
end end
describe '#errors' do describe "#errors" do
subject(:errors) { result.errors } subject(:errors) { result.errors }
let(:params) do let(:params) do
double(:params, message_set: [], to_h: { email: 'jane@doe.org' }) double(:params, message_set: [], to_h: {email: "jane@doe.org"})
end end
let(:result) do let(:result) do
Dry::Validation::Result.new(params) do |r| Dry::Validation::Result.new(params) do |r|
r.add_error(Dry::Validation::Message.new('root error', path: [nil])) r.add_error(Dry::Validation::Message.new("root error", path: [nil]))
r.add_error(Dry::Validation::Message.new('email error', path: [:email])) r.add_error(Dry::Validation::Message.new("email error", path: [:email]))
end end
end end
describe '#[]' do describe "#[]" do
it 'returns error messages for the provided key' do it "returns error messages for the provided key" do
expect(errors[:email]).to eql(['email error']) expect(errors[:email]).to eql(["email error"])
end end
it 'returns [] for base errors' do it "returns [] for base errors" do
expect(errors[nil]).to eql(['root error']) expect(errors[nil]).to eql(["root error"])
end end
end end
describe '#empty?' do describe "#empty?" do
let(:result) { Dry::Validation::Result.new(params) } let(:result) { Dry::Validation::Result.new(params) }
it 'should return the correct value whilst adding errors' do it "should return the correct value whilst adding errors" do
expect(result.errors).to be_empty expect(result.errors).to be_empty
result.add_error(Dry::Validation::Message.new('root error', path: [nil])) result.add_error(Dry::Validation::Message.new("root error", path: [nil]))
expect(result.errors).not_to be_empty expect(result.errors).not_to be_empty
end end
end end
end end
describe '#inspect' do describe "#inspect" do
let(:params) do let(:params) do
double(:params, message_set: [], to_h: {}) double(:params, message_set: [], to_h: {})
end end
let(:context) do let(:context) do
context = Concurrent::Map.new context = Concurrent::Map.new
context[:data] = 'foo' context[:data] = "foo"
context context
end end
@ -65,7 +65,7 @@ RSpec.describe Dry::Validation::Result do
Dry::Validation::Result.new(params, context) Dry::Validation::Result.new(params, context)
end end
example 'results are inspectable' do example "results are inspectable" do
expect(result.inspect).to be_a(String) expect(result.inspect).to be_a(String)
end end
end end

View File

@ -1,30 +1,30 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'support/coverage' require_relative "support/coverage"
require_relative 'support/warnings' require_relative "support/warnings"
begin begin
require 'pry' require "pry"
require 'pry-byebug' require "pry-byebug"
rescue LoadError rescue LoadError
end end
require 'i18n' require "i18n"
require 'dry/validation' require "dry/validation"
SPEC_ROOT = Pathname(__dir__) SPEC_ROOT = Pathname(__dir__)
Dir[SPEC_ROOT.join('support/**/*.rb')].each(&method(:require)) Dir[SPEC_ROOT.join("support/**/*.rb")].each(&method(:require))
RSpec.configure do |config| RSpec.configure do |config|
unless RUBY_VERSION >= '2.7' unless RUBY_VERSION >= "2.7"
config.exclude_pattern = '**/pattern_matching_spec.rb' config.exclude_pattern = "**/pattern_matching_spec.rb"
end end
config.disable_monkey_patching! config.disable_monkey_patching!
config.before do config.before do
stub_const('Test', Module.new) stub_const("Test", Module.new)
end end
config.after do config.after do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'dry/validation/values' require "dry/validation/values"
RSpec.describe Dry::Validation::Values do RSpec.describe Dry::Validation::Values do
subject(:values) do subject(:values) do
@ -8,92 +8,92 @@ RSpec.describe Dry::Validation::Values do
end end
let(:data) do let(:data) do
{ name: 'Jane', address: { city: 'Paris', geo: { lat: 1, lon: 2 } }, phones: [123, 431] } {name: "Jane", address: {city: "Paris", geo: {lat: 1, lon: 2}}, phones: [123, 431]}
end end
describe '#[]' do describe "#[]" do
it 'works with a symbol' do it "works with a symbol" do
expect(values[:name]).to eql('Jane') expect(values[:name]).to eql("Jane")
end end
it 'works with a dot-notation path' do it "works with a dot-notation path" do
expect(values['address.city']).to eql('Paris') expect(values["address.city"]).to eql("Paris")
end end
it 'works with a path' do it "works with a path" do
expect(values[:address, :city]).to eql('Paris') expect(values[:address, :city]).to eql("Paris")
end end
it 'works with a hash' do it "works with a hash" do
expect(values[address: :city]).to eql('Paris') expect(values[address: :city]).to eql("Paris")
end end
it 'works with a hash pointing to multiple values' do it "works with a hash pointing to multiple values" do
expect(values[address: { geo: [:lat, :lon] }]).to eql([1, 2]) expect(values[address: {geo: [:lat, :lon]}]).to eql([1, 2])
end end
it 'works with an array' do it "works with an array" do
expect(values[%i[address city]]).to eql('Paris') expect(values[%i[address city]]).to eql("Paris")
end end
it 'raises on unpexpected argument type' do it "raises on unpexpected argument type" do
expect { values[123] } expect { values[123] }
.to raise_error( .to raise_error(
ArgumentError, '+key+ must be a valid path specification' ArgumentError, "+key+ must be a valid path specification"
) )
end end
it 'accepts missing keys returning nil' do it "accepts missing keys returning nil" do
expect(values[address: { geo: [:population, :lon] }]).to eql([nil, 2]) expect(values[address: {geo: [:population, :lon]}]).to eql([nil, 2])
end end
end end
describe '#key?' do describe "#key?" do
it 'returns true when a symbol key is present' do it "returns true when a symbol key is present" do
expect(values.key?(:name)).to be(true) expect(values.key?(:name)).to be(true)
end end
it 'returns false when a symbol key is not present' do it "returns false when a symbol key is not present" do
expect(values.key?(:not_here)).to be(false) expect(values.key?(:not_here)).to be(false)
end end
it 'returns true when a nested key is present' do it "returns true when a nested key is present" do
expect(values.key?([:address, :city])).to be(true) expect(values.key?([:address, :city])).to be(true)
end end
it 'returns false when a nested key is not present' do it "returns false when a nested key is not present" do
expect(values.key?([:address, :not_here])).to be(false) expect(values.key?([:address, :not_here])).to be(false)
end end
it 'returns true when nested keys are all present' do it "returns true when nested keys are all present" do
expect(values.key?([:address, :geo, [:lat, :lon]])).to be(true) expect(values.key?([:address, :geo, [:lat, :lon]])).to be(true)
end end
it 'returns false when nested keys are not all present' do it "returns false when nested keys are not all present" do
expect(values.key?([:address, :geo, [:lat, :lon, :other]])).to be(false) expect(values.key?([:address, :geo, [:lat, :lon, :other]])).to be(false)
end end
it 'returns true when a path to an array element is present' do it "returns true when a path to an array element is present" do
expect(values.key?([:phones, 1])).to be(true) expect(values.key?([:phones, 1])).to be(true)
end end
it 'returns false when a path to an array element is not present' do it "returns false when a path to an array element is not present" do
expect(values.key?([:phones, 5])).to be(false) expect(values.key?([:phones, 5])).to be(false)
end end
end end
describe '#dig' do describe "#dig" do
it 'returns a value from a nested hash when it exists' do it "returns a value from a nested hash when it exists" do
expect(values.dig(:address, :city)).to eql('Paris') expect(values.dig(:address, :city)).to eql("Paris")
end end
it 'returns nil otherwise' do it "returns nil otherwise" do
expect(values.dig(:oops, :not_here)).to be(nil) expect(values.dig(:oops, :not_here)).to be(nil)
end end
end end
describe '#method_missing' do describe "#method_missing" do
it 'forwards to data' do it "forwards to data" do
result = [] result = []
values.each do |k, v| values.each do |k, v|
@ -103,14 +103,14 @@ RSpec.describe Dry::Validation::Values do
expect(result).to eql(values.to_a) expect(result).to eql(values.to_a)
end end
it 'raises NoMethodError when data does not respond to the meth' do it "raises NoMethodError when data does not respond to the meth" do
expect { values.not_really_implemented } expect { values.not_really_implemented }
.to raise_error(NoMethodError, /not_really_implemented/) .to raise_error(NoMethodError, /not_really_implemented/)
end end
end end
describe '#method' do describe "#method" do
it 'returns Method objects for a forwarded method' do it "returns Method objects for a forwarded method" do
expect(values.method(:dig)).to be_instance_of(Method) expect(values.method(:dig)).to be_instance_of(Method)
end end
end end