We have a few transactions that all take a “message” as input, along with a few other arguments that differ. In dry-validations < 1.0, we did something like this:
MessageSchema = Dry::Validation.Params do
optional(:body).maybe(:str?)
optional(:attachments).each do
schema do
required(:key).filled(:str?)
required(:size).filled(:int?)
required(:content_type).filled(:str?)
optional(:width).maybe(:int?)
optional(:height).maybe(:int?)
end
end
optional(:saved_reply).maybe
optional(:flags).maybe(:array?)
optional(:scheduled_at).maybe(:time?, :in_the_future?)
validate(has_content: [:body, :attachments]) do |body, attachments|
body.present? || attachments.present?
end
end
# Then in each transaction, we have a validation like this, each with different
`required` params:
Dry::Validation.Schema do
required(:conversation).filled
required(:user).filled
required(:message).schema(MessageSchema)
end
I’m trying to upgrade to dry-schema and dry-validations, and I’m having trouble accomplishing this. I can change it to required(:message).hash(MessageSchema), but I have to change MessageSchema to a dry-schema, and there’s no way to accomplish the :in_the_future? and body.present? || attachments.present? checks. If I convert MessageSchema to a Contract, I can’t pass it to required(:message).hash(MessageContract), it fails with undefined methodto_ast’ for #MessageContract:0x000055d600d0fc38`.
Is it possible to compose validations like this? I could write a :valid_message? macro that does MessageContract.new.call(value), but that would lose the validation error messages on message, wouldn’t it?
require 'dry/validation'
class AppContract < Dry::Validation::Contract
register_macro(:has_content?) do |macro:|
if values[:body].nil? && values[:attachments].nil?
key.failure("must have either body or attachments")
end
end
end
MessageSchema = Dry::Schema.Params do
optional(:body).maybe(:string)
optional(:attachments).array(:hash) do
required(:key).filled(:string)
end
end
class OtherContract < AppContract
params do
required(:message).schema(MessageSchema)
end
rule(:message).validate(:has_content?)
end
contract = OtherContract.new
puts contract.(message: { body: '', attachments: [] }).inspect
#<Dry::Validation::Result{:message=>{:body=>nil, :attachments=>[]}} errors={:message=>["must have either body or attachments"]}>
On our case, we have several contracts that have different sets of args, but all have the same Message. So we might have
ContractA
required(:x).filled
required(:y).filled
required(:message).schema(MessageSchema)
rule(:message).validate(:has_content?)
end
ContractB
required(:x).filled
required(:z).filled
required(:message).schema(MessageSchema)
rule(:message).validate(:has_content?)
end
Given that we re-use that same “message validation” as a portion of a handful of other contracts, we’d have to be sure to specify both the schema and rule in every case?
Is there a feature on the roadmap to make them composable in a single shared validation/schema?