Validating ordered array of multiple types

I’ve been looking at the docs and other posts here, but I’ve been unable to determine if it’s possible to do what I want. In the dry-schema docs, they show array validation as being able to validate all of the array’s members as the same type. What I’m looking to do is what the array.ordered validation in Joi can do: https://hapi.dev/module/joi/api/?v=17.1.1#arrayorderedtype

I have a data type similar to:

{
  items: [
    {
      id: 'asdf',
      thing: '1234'
    },
    {
      id: 'jkl',
      thing: '2345'
    }
  ]
}

I’m trying to validate that:

  1. There are exactly 2 items in the items array.
  2. The first member has an id of exactly ‘asdf’ and a thing that is a string that can be converted from string to an integer and back to string again, and remain the exact same value.
  3. The second member has an id of exactly ‘jkl’ and a thing that is a string that can be converted from string to an integer and back to string again, and remain the exact same value.

Can anyone give me some advice on whether this is possible, and if it is how to code it up?

Edit: I should say, I can see how to validate on a hash in isolation that it has the exact ID and how to make a rule for the specific conditions around the string->integer->string thing field.

You could do this:

require 'dry/validation'

class Contract < Dry::Validation::Contract
  params do
    required(:items).value(:array, size?: 2).each(:hash) do
      required(:id).value(:string)
      required(:thing).value(:integer)
    end
  end

  rule(:items) do
    key([:items, 0, :id]).failure('id must be asdf') unless value[0][:id].eql?('asdf')
    key([:items, 1, :id]).failure('id must be jkl') unless value[1][:id].eql?('jkl')
  end
end

data = {
  items: [
    {
      id: 'asdf',
      thing: '1234'
    },
    {
      id: 'jkl',
      thing: '2345'
    }
  ]
}

contract = Contract.new

puts contract.(data).errors.to_h.inspect
# {}

data = {
  items: [
    {
      id: 'foo',
      thing: '1234'
    },
    {
      id: 'bar',
      thing: '2345'
    }
  ]
}

contract = Contract.new

puts contract.(data).errors.to_h.inspect
# {:items=>{0=>{:id=>["id must be asdf"]}, 1=>{:id=>["id must be jkl"]}}}

I think that’ll get me 90% of what I want, thanks @solnic!