Nested params schema with rules

Hi! I’m upgrading dry-validation and dry-types in a project to 1.0 (and dry-struct to 1.1) and I’m having trouble figuring out how to migrate code like this:

Dry::Validation.Params do
  optional(:foo).maybe(:hash?) do
    schema do
      optional(:bar).maybe(:bool?)
      optional(:baz).maybe(:bool?)
      optional(:qux).maybe(:str?)

      rule(bar: [:baz, :qux]) do |baz, qux|
        baz.true?.then(qux.filled?)
      end
    end
  end
end

I can migrate the schema to Dry::Struct.Params and other code but the rules seem to be problematic since dry-struct doesn’t support them. I’ve tried to use a contract instead but I don’t see any way to have dependencies on the hash keys like the code above (where bar, baz and qux are keys on the foo hash, and their rules depend on each other).

Is this type of thing not supported out-of-the-box anymore? If so, what would be the most “dry” way to achieve it?

Thanks for any help, and thanks for the great gems! :smile:

1 Like

It would seem that this feature has been removed, but I’d still like to know if there is any workaround. My only solution right now is to define the rule on the parent with fairly complex logic to reproduce the original messages with key.failure, etc.

The closest you can get now is this:

require "dry/validation"

class Contract < Dry::Validation::Contract
  params do
    optional(:foo) do
      nil?.or(
        hash do
          optional(:bar).maybe(:bool)
          optional(:baz).maybe(:bool)
          optional(:qux).maybe(:string)
        end
      )
    end
  end

  rule(foo: [:baz, :qux]) do
    if values[:foo]
      baz, qux = value

      if baz && (qux.nil? || qux.empty?)
        key("foo.bar").failure("qux must be filled when baz is true")
      end
    end
  end
end

contract = Contract.new

puts contract.(foo: nil).errors.to_h.inspect
# {}

puts contract.(foo: { bar: true, baz: true, qux: "" }).errors.to_h.inspect
# {:foo=>{:bar=>["qux must be filled when baz is true"]}}

Unfortunately, maybe(:hash) do ... end doesn’t work yet, that’s why you need this nil?.or(hash do...end) thingie for the time being. I just reported an issue about this.

1 Like

Thanks very much! That’s more or less what I had.

About the maybe, are these two equivalent?

optional(:foo) do
  nil?.or(
    hash do
      ...
    end
  end
end

and

optional(:foo).maybe do
  hash do
    ...
  end
end

Just checking if my understanding of the DSL is correct…

yes it is

1 Like