Hi there! I have a question about external dependencies section,
On the page linked above, you can see AddressValidator being used, to validatate the address. However, at the end, we loose all the information related to the original address validation failure details.
Is there a way, to use nested/dynami contracts, but pass the error information to the parent’s failure?
TIP: In the 0,13 you could do sth like this:
# frozen_string_literal: true
module Schemas
module Reports
Address1Schema = Dry::Schema.define do
required(:city).filled(:str?)
end
Address2Schema = Dry::Schema.define do
optional(:city).filled(:str?)
end
UserSchema = Dry::Schema.define do
required(:address_type).filled(:str?)
required(:address).filled?(:hash?)
rule(address: [:address, :address_type]) do |address, address_type|
# s3 -> S3Source
address_type.eql?('1').then(
data_source_params.schema(Address1Schema)
) &
addresss_type.eql?('2').then(
data_source_params.schema(Address2Schema)
)
end
end
end
end
But with “new” syntax, where schemas were extracted, now the key.failure
needs to be invoked. This accepts string
as a message, and I can’t find a way to pass the details of the Address1Schema validation.
PS: I know it’s not the best example, I just need to call dynamic validator and pass the error details.
Thank you for help! cc: @solnic @flash-gordon
This is perhaps a problem with the documentation, calling the dependency address_validator
may give the impression that this is intended to allow composing contracts together.
I don’t believe this is the case at all. It’s essentially just dry-initializer
exposed as public API so that you can inject dependencies. I think the use-case for this is for injecting data that your rule
s consume for defining contract validations.
Here’s an (abridged) example of a contract I wrote that validates currency codes
module Billing
module Contracts
class Product < Contract
option :currency_codes, default: -> { Billing.config.currency.codes }
schema do
optional(:line_items).array do
schema do
required(:key).value(Types::LineItem::Key)
required(:price).schema do
required(:unit).value(Types::Denomination)
end
optional(:code).value(:string)
optional(:quantity).value(Types::Positive)
end
end
end
rule(:line_items).each do
next unless (denomination = value[:price][:unit]).is_a?(String)
unless currency_codes.include?(denomination)
key.failure("unknown currency: #{denomination}")
end
end
end
end
end
1 Like
At the moment I weite random examples, to not bothering with NDA, but the use case is that we have a settings loader, which loads YAML defined step ctures, that may be nested to several levels and tricky sometimes.
For example, you can configure file storage - and this should use different structure validator depending on the chosen type.
This is why I need this and in the older version it was supported, so I wondered how to keep the functionality untouched after upgrading to new dry-validation.
I solved it for now but in a very dirty way, mapping nested contract errors and setting proper key failure for each.
I understand, you want to dynamically apply a schema to a subkey at runtime.
I don’t believe that is supported officially but it’s not hard to make that happen:
#!/usr/bin/env ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'dry-validation'
end
require 'dry/validation'
Types = Dry::Types(default: :strict)
module Types
Filled = String.constrained(filled: true)
end
module AddressSchema
V1 = Dry::Schema.define do
required(:city).value(Types::Filled)
end
V2 = Dry::Schema.define do
optional(:city).value(Types::Filled)
end
end
class UserSchema < Dry::Validation::Contract
AddressType = Types::String.enum('V1', 'V2')
schema do
required(:address_type).value(AddressType)
required(:address).hash
end
rule :address_type, :address do
schema = AddressSchema.const_get(values[:address_type])
schema.(values[:address]).errors.each do |msg|
key(:address).opts << {message: msg.text, path: [:address, *msg.path], tokens: {}}
end
end
end
puts UserSchema.new.({ address_type: 'V1', address: { street_address: '123 main st' }}).errors.to_h
Thank you! This is exactly how I solved this in my project, however it looks ugly, and I thought there may be a more official way that I can’t figure out - especially because it was supported out of the box in the past versions.
Thanks for the confirmation, I’ll stick with it for now.
Perhaps you could make a macro that hides this, if not I would write a refinement/extension to the Key object.
something like
key(:address).schema(AddressSchema::V1)
should be easy enough to implement
1 Like