dry-validation/spec/integration/contract/class_interface/rule/nested_data_spec.rb

136 lines
4.0 KiB
Ruby

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