Is this possible to do with dry-validation?

Hi there, I’m trying to use Dry::Validation to validate a JSON-API service and I’m running into a roadblock with the data member.

According to the spec, data must either be a single resource object, or an array of resource objects.

I have a Resource validation that looks like

Resource = Dry::Validation.JSON do
  configure { config.type_specs = true }

  required(:type, "strict.string").filled
  required(:id, "coercible.integer").filled
  optional(:attributes, Types::Hash)
  optional(:relationships, Types::Hash)
  optional(:links, Types::Links)
  optional(:meta, Types::Hash)
end

And now I’m trying to embed this into a larger Document validation but I have no idea how to express an object or array validation.

Document = Dry::Validation.JSON do
  configure { config.type_specs = true }

  required(:data).each { schema Resource }

  optional(:included).each { schema Resource }

  optional(:links, Types::Links)
  optional(:meta, Types::Hash)

  optional(:jsonapi, Types::Hash)
end

I’ve looked at a lot of example validations, and I have never seen one like I need. Is this even possible?

Something like this:

required(:data) { each(schema(Resource)) | schema(Resource) }
1 Like

No luck :slightly_frowning_face:

Doing what you suggest gives me:

Failure/Error: each(schema(Resource)) | schema(Resource)

ArgumentError:
  is_a? predicate arity is invalid

I suspect that each doesn’t support passing an argument like that… so I tried:

required(:data) do
  schema(Resource) | each { schema(Resource) }
end

This compiles, however now it fails no matter what I pass in:

subject.(data: { id: "123", type: "foo" })
=> Failure({:data=>["data is missing"]})

subject.(data: [{ id: "123", type: "foo" }])
=> Failure({:data=>["data is missing"]})

Followup for any unlucky soul who got here via search engine:

dry-schema 1.0 is mostly able to handle this use-case :tada:

class Identity < Dry::Schema::JSON
  define do
    required(:id).filled { str? | int? }
    required(:type).filled(:string)
  end
end

class Resource < Identity
  define do
    optional(:attributes).hash
    optional(:relationships).hash
    optional(:links).hash
    optional(:meta).hash
  end
end

class Document < Dry::Schema::JSON
  define do
    required(:data) do
      array { schema(Resource.new) } | schema(Resource.new)
    end

    optional(:included).array { schema(Resource.new) }
    optional(:links).hash
    optional(:meta).hash
    optional(:jsonapi).hash
  end
end

There are two places where Dry::Schema still falls short: relationships and links both can contain arbitrarily-named keys. In Dry::Types you would be tempted to express these as Map types, but that is currently not supported.

The ability to express dynamic key names is currently slated for 1.4.

1 Like