dry-schema: split string, validate elements against enum

Hi,

I get a parameter named includes as a comma-separated string, and want to validate that all its elements are within a set of allowed values. I believe, this is conceptually similar to this question, where an input string is coerced into an array of hashes.

I feel like I’m close, but something’s missing:

require "dry-schema"

module T
  include Dry.Types()

  CommaSepString = T.Constructor(Array) do |values|
    values.split(",")
  end
end

pp T::CommaSepString.("a,b,c")
#=> ["a", "b", "c"]

schema = Dry::Schema.Params do
  optional(:includes).value(T::CommaSepString, included_in?: %w[users favorites])
end

pp schema.("includes" => "users,favorites")
#=> #<Dry::Schema::Result{:includes=>["users", "favorites"]} errors={:includes=>["must be one of: users, favorites"]} path=[]>

I’ve also tried the array macro:

schema = Dry::Schema.Params do
  optional(:includes).array(T::CommaSepString, included_in?: %w[users favorites])
end

pp schema.("includes" => "users,favorites")
# #<Dry::Schema::Result{:includes=>"users,favorites"} errors={:includes=>["must be an array"]} path=[]>

Your problem is that you’re applying the constraint against the whole array rather than the individual elements.

Here how I solved a very similar problem

T = Dry.Types()

module T
  Inclusion = String.enum('users', 'favorites')

  module Params
    CSVList = T::Array.of(String).constructor do |value|
      if value.is_a?(::String)
        value.split(',')
      else
        Array(value)
      end
    end
  end
end


schema = Dry::Schema.Params do
  optional(:includes).value(T::Params::CSVList.of(T::Inclusion))
end
[2] pry(main)> schema.({ includes: 'users,favorites' })
=> #<Dry::Schema::Result{:includes=>["users", "favorites"]} errors={} path=[]>
[3] pry(main)> schema.({ includes: 'users,favorites,whatever' })
=> #<Dry::Schema::Result{:includes=>["users", "favorites", "whatever"]} errors={:includes=>{2=>["must be one of: users, favorites"]}} path=[]>
[4] pry(main)>