dry-validation icon indicating copy to clipboard operation
dry-validation copied to clipboard

Support for array items in rule paths

Open solnic opened this issue 4 years ago • 9 comments

Examples

require 'dry-validation'

class PropsContract < Dry::Validation::Contract
  json do
    required(:contacts).value(:array, min_size?: 1).each do
      hash do
        required(:name).filled(:string)
        required(:email).filled(:string)
        required(:phone).filled(:string)
      end
    end
  end

  rule("contacts[].email").each do
    key.failure("email not valid") unless value.include?('@')
  end
end

c = PropsContract.new

c.(
  contacts: [
    { name: 'Jane', email: '[email protected]', phone: '123' },
    { name: 'John', email: 'oops', phone: '123' }
  ]).errors.to_h.inspect
# {:email=>{1=>["email not valid"]}}

Resources

Refs #603

solnic avatar Dec 05 '19 10:12 solnic

@solnic shouldn't the rule be without .each?

  rule("contacts[].email") do
    key.failure("email not valid") unless value.include?('@')
  end

  rule("contacts[].emails").each do
    key.failure("email not valid") unless value.include?('@')
  end

skryukov avatar Dec 08 '19 08:12 skryukov

@skryukov I think it should be with each, because otherwise we'd have to infer each from the path anyway. I prefer to keep it explicit.

solnic avatar Dec 08 '19 10:12 solnic

@solnic I see your point, but how we will differentiate, should we iterate over emails or use email as a target item for the rule? Maybe we should introduce another method to explicitly tell dry-v to find array items inside a rule?

skryukov avatar Dec 09 '19 17:12 skryukov

@skryukov wait, I think I’m missing something - differentiate what?

solnic avatar Dec 09 '19 17:12 solnic

@solnic I'll try to explain =)

For example, we have a schema like this:

require 'dry-validation'

class PropsContract < Dry::Validation::Contract
  json do
    required(:contacts).value(:array, min_size?: 1).each do
      hash do
        required(:name).filled(:string)
        required(:email).filled(:string)
        required(:phone).filled(:string)
        required(:additional_emails).each(:string)
      end
    end
  end

  rule("contacts[].email").each do
    key.failure("email not valid") unless value.include?('@')
  end

  rule("contacts[].additional_emails").each do
    key.failure("email not valid") unless value.include?('@')
  end
end

c = PropsContract.new

c.(
  contacts: [
    { name: 'Jane', email: '[email protected]', phone: '123', additional_emails: ['oops'] },
    { name: 'John', email: 'oops', phone: '123', additional_emails: ['[email protected]'] }
  ]).errors.to_h.inspect
# => {:contacts =>{0=>{:additional_emails =>{0=>["email not valid"]}}},1=>{:email=>["email not valid"]}}

So how should we know when a rule needs to be applied to the item itself (email) and when on elements of the item (additional_emails)? And what if I want to check something like this:

  rule("contacts[].additional_emails").each(index:) do # or maybe even indexes in this case? =)
    key.failure("additional emails contain primary email") if value.include?(values[:contacts][index][:email])
  end

skryukov avatar Dec 09 '19 19:12 skryukov

One more thing, contacts[].values[] <- how would that work? :) It's probably better to reject such paths

flash-gordon avatar Dec 09 '19 22:12 flash-gordon

I don't know. Please feel free to figure it out however you think is best 😄

solnic avatar Dec 10 '19 09:12 solnic

Hello, I think my question suits this issue as well

I have a macro:

register_macro(:numeric_string?) do
        condition = /^(\d)+$/.match?(value)
        message = 'must be a numeric string'
        key(keys.flatten).failure(message) unless condition
end

and I have to check in the same file both a single case such as the following:

rule(%i[data attributes token]).validate(:numeric_string?)

and for a separate case also array of numeric strings:

rule(%i[data attributes atrray_of_strings]).each do |r|
  r.validate(:numeric_string?)
end

the last one does not work

is there a right way how to do that? if not, might be it should be added to the next versions?

Many thanks in advance

TimoMoss avatar Nov 26 '20 12:11 TimoMoss

@TimoMoss for now you need to handle it manually, well implement a nice DSL for it at some point. For now I'm adding this to 1.7.0 milestone.

solnic avatar Dec 08 '20 07:12 solnic