Giter Site home page Giter Site logo

dry-rb / dry-validation Goto Github PK

View Code? Open in Web Editor NEW
1.3K 37.0 185.0 2.29 MB

Validation library with type-safe schemas and rules

Home Page: https://dry-rb.org/gems/dry-validation

License: MIT License

Ruby 98.95% HTML 1.05%
dry-rb validation ruby data-validation type-safety coercion rubygem gem

dry-validation's Introduction

dry-validation Gem Version CI Status

Links

Supported Ruby versions

This library officially supports the following Ruby versions:

  • MRI >= 3.0
  • jruby >= 9.4 (not tested on CI)

License

See LICENSE file.

dry-validation's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dry-validation's Issues

Seems like `size?(range)` doesn't work

I have the following validator:

key(:region_iso_code) { |v| v.str? && v.size?(1..3) }

it raise the following error:

{:region_iso_code=>[["region_iso_code length must be within 1 - 3", "MOS"]]}

As you can see the input value MOS doesn't pass validation. Is it bug?

Lots of RuboCop offenses

I would be happy to help either disable some of the active RuboCop inspections, or change the code to follow the default inspections.

Do you guys have any preference on how to solve all of these?

Rules AST no longer includes "set" elements for nested keys

Firstly, as you know, I'm a big fan :) And I'm looking to make formalist integrate more closely with dry-validation right now.

In formalist, we work with the dry-validation rules AST in order to incorporate schema rule information into our form elements (with the idea being that front-end form renderers can include matching client-side validation or hint text).

For schemas with nested keys, we rely on a :set element being present so we can look for that and use it as a why to go deeper into the nested keys. However, it seems like these :set elements have disappeared with your work since 0.6.0.

For example, with this little example:

class OneLevelSchema < Dry::Validation::Schema
  key(:outer) { |outer|
    outer.key(:inner, &:filled?)
  }
end

one = OneLevelSchema.new
pp one.rules.map(&:to_ary)

On 0.6.0, we see this output:

[[:and,
  [[:key, [:outer, [:predicate, [:key?, [:outer]]]]],
   [:set,
    [:outer,
     [[:and,
       [[:key, [:inner, [:predicate, [:key?, [:inner]]]]],
        [:val, [:inner, [:predicate, [:filled?, []]]]]]]]]]]]]

But on master, we see this:

[[:and,
  [[:key, [:outer, [:predicate, [:key?, [:outer]]]]],
   [:and,
    [[:key, [:inner, [:predicate, [:key?, [:inner]]]]],
     [:val, [:inner, [:predicate, [:filled?, []]]]]]]]]]

Is this a regression/bug, or is the removal of the :set element here intentional?

Is there a way to programatically figure out which predicate failed?

Hi!
First of all, thanks for the great gem! I hope AciverRecord will die one day(it's dying hard...).

I'd like to know, s there a way to programatically figure out which predicate failed?
Result of validation looks like this:

{ :email => ["email must be filled"] }

Basically, it's the same of ActiveModel::Errors#messages returns: attribute and human readable messages.

The use case where it is not enough: http API, where every type of validation error may result into different code.
I am developing JSON API, and it would be greate to give to API consumers ability to distinguish between different validation failure, because they may want to handle it in different way.

In general, result in such format would be enough:

{ 
  :email => {
    :filled => "email must be filled"
  }
}

Thanks!

Form: When a single validation isn't respected for an attribute, it returns all the errors

Hello ๐Ÿ˜„

I'm following the instructions from examples/form.rb

require 'bundler/setup'
require 'dry-validation'
require 'dry/validation/schema/form'

class UserFormSchema < Dry::Validation::Schema::Form
  key(:email) {|value| value.str? & value.filled? }
  key(:age)   {|value| value.int? & value.gt?(18) }
end

schema = UserFormSchema.new
errors = schema.call({'email' => '', 'age' => '18'})

puts errors.inspect
puts ""
puts errors.messages.inspect

__END__

#<Dry::Validation::Schema::Result params={:email=>"", :age=>18} messages={:age=>[["age must be greater than 18", "age must be an integer"], 18], :email=>[["email must be filled"], ""]}>

{:age=>[["age must be greater than 18", "age must be an integer"], 18], :email=>[["email must be filled"], ""]}

There is only one violation for age: 18 isn't greater than 18. But it also reports the other error "age must be an integer".

If I do:

errors = schema.call({'email' => '', 'age' => '19'})

__END__

#<Dry::Validation::Schema::Result params={:email=>"", :age=>19} messages={:email=>[["email must be filled"], ""]}>

{:email=>[["email must be filled"], ""]}

It accepts the string representation of the age "19" as an integer.

Generate error messages

Add some way to generate readable error messages (for dev/debug purposes), inspecting the output error set is hard when there is many rules and nested input.

Problem with own predicates

Based on example in README

module MyPredicates
  include Dry::Validation::Predicates

  predicate(:uuid?) do |input|
    ! /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.match(input).nil?
  end
end

class Schema < Dry::Validation::Schema
  configure do |config|
    config.predicates = MyPredicates
  end

  key(:id) { |value| value.str? & value.uuid? }
end

When I am writing:

Schema.new.messages({ id: "aaa" })

I always receive:

NoMethodError: undefined method `visit_uuid?' for #<Dry::Validation::ErrorCompiler:0x007f83c40c31f0>

Non-failing validation returns `nil` messages instead of empty Array

Given the example from the Readme (which was working in 0.2):

require 'dry-validation'

class Schema < Dry::Validation::Schema
  key(:email) { |email| email.filled? }

  key(:age) do |age|
    age.none? | (age.int? & age.gt?(18))
  end
end

schema = Schema.new

errors = schema.call(email: '[email protected]', age: nil).messages

puts errors.inspect
# []

The errors variable should be [] but is nil on 0.3.0

Help with "polymorphic" Hash validation

Hi @solnic!

I'm using dry-validation to validate JSON body of my api and is working great!
I'm in doubt on one specific scenario, for example:

"payments": [
    {
      "method": "billet",
      "amount": 50.85
    },
    {
      "method": "credit_card",
      "amount": 134.9,
      "installments_quantity": 5,
      "installments_tax": 3.9,
      "installments_amount": 26.98
    }
  ]

When method is credit_card, i need to validate installments fields as required. Do you have any idea of how can i validate this? My schema source is:

      RequiredString = ->(value) { value.filled? & value.str? }
      RequiredAmount = ->(value) { value.filled? & value.float? }

      key("payments") do |payments|
        payments.array? do
          payments.each do |payment|
            payment.hash? do
              payment.key("method", &RequiredString)
              payment.key("amount", &RequiredAmount)
            end
          end
        end
      end

Errors for `each` blocks with multiple keys are not complete

With a schema like this, which has an each block with rules for 2 nested keys:

schema = Dry::Validation.Form do
  key(:reviews).each do
    key(:summary).required
    key(:rating).required(:int?)
  end
end

When I call it with a totally empty object, I get the error back for only one of those keys, when it should really be showing me errors for both (summary and rating):

schema.("reviews" => [{}]).messages
# => {:reviews=>{0=>{:summary=>["is missing"]}}}

Add new predicates

We just have 8 basic predicates that I wrote pretty much only for testing purposes, but obviously we need a ton more to cover common functionality, like (from the top of my head):

  • size?(num) - matches exact size
  • format?(regex) - matches a string against a regexp
  • inclusion?(list) - checks if a given value is included in the provided list (although I should say this feels too low level and typically I'd probably prefer a custom predicate that would re-use this one)
  • lt?(num)- matches if a given input is less than the specified one
  • lte?(num) - matches if a given input is less than or equal to the specified one
  • gte?(num) - self-explanatory
  • eql?(anything) - matches exact equality
  • within?(range) - matches if the input is within the given range

I'm inclined to provide higher-level predicates too, like email? etc.

Please tell me if something is missing :)

`visit': undefined method `visit_#<Dry::Validation::Schema::Rule:0x00000001ee9098>' for #<Dry::Validation::RuleCompiler:0x00000001ee8648> (NoMethodError)

/home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:18:in `visit': undefined method `visit_#<Dry::Validation::Schema::Rule:0x00000001ee9098>' for #<Dry::Validation::RuleCompiler:0x00000001ee8648> (NoMethodError)
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:38:in `visit_each'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:18:in `visit'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:48:in `visit_and'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:18:in `visit'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:48:in `visit_and'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:18:in `visit'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:13:in `block in call'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:13:in `map'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:13:in `call'
        from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/schema.rb:51:in `initialize'
        from r.rb:26:in `new'
        from r.rb:26:in `<main>'
require 'json'
require 'dry-validation'

class StatSchema < Dry::Validation::Schema
  key(:stats) do |root|
    root.filled?
    root.hash? do
      root.each do |s|
        s.hash?
        s.key(:strength, &:filled?)
      end
    end
  end
end

json = JSON.parse <<-EOT
{
    "stats": {
        "strength": {
            "description": "Increases physical damage"
        }
    }
}
EOT

schema = StatSchema.new

Any help would be great, thanks!

Abstract schema and form/schema in order to manually specify rules

We are generating validation rules by converting our in-house schema to the dry-validation rule AST and then compile it into a an array of rules. Basically we want to have our rules in a Dry::Validation::Schema[::Form] object so we can reuse the validation process and error compilation that dry-validation provides.

Currently there is no way to do it properly, our options is to duplicate whatever Dry::Validation::Schema does or resort to monkey patching.

Error messages for groups (like the "confirmation" short-cut) can't be generated

If I have a schema like this:

class MySchema < Dry::Validation::Schema
  confirmation(:password)  
end

And call it with invalid confirmation data:

v = MySchema.new.call(password: "foo", password_confirmation: "bar")

Then it looks like it's not possible to provide YML or i18n data to provide a human-friendly error message for that validation failure.

The reason for this is that the error AST entry for that failure has a :group in it:

pry(main)> v.errors.map(&:to_ary)
# => [[:error, [:input, [:password_confirmation, ["foo", "bar"], [[:group, [:password_confirmation, [:predicate, [:eql?, []]]]]]]]]]

And the ErrorCompiler has no #visit_group method, which means it goes through to #method_missing and just returns {value: args[1]} output for the validation result's #messages, i.e.

pry(main)> v.messages
# => {:password_confirmation=>[[{:value=>:password_confirmation}], ["foo", "bar"]]}

Because it goes through #method_missing in the error compiler, it means it never actually tries to look up an i18n/yml key for the translated error message for that validation failure.

Is my understanding of this correct? Is this just a matter of updating the ErrorCompiler to support groups? Or have I missed some already existing way to actually provide human-friendly error messages for failures like this?

Thanks :)

How I can get index of element with errors in array

Hello!

In your example of validation elements in array we can't get index of element in array where is error.
How I can get index?

class Schema < Dry::Validation::Schema
  key(:phone_numbers) do |phone_numbers|
    phone_numbers.array? do
      phone_numbers.each(&:str?)
    end
  end
end

schema = Schema.new
errors = schema.call(phone_numbers: ['123456789', 123456789]).messages
puts errors.inspect
# {
#   :phone_numbers => [
#     {
#       :phone_numbers => [
#         ["phone_numbers must be a string", 123456789]  << Error in element with index 1
#       ]
#     }
#   ]
# }

Array validation crashes

Array validation is expected to return validation error if value is not an array, but now it crashes.

dry-validation 0.4.1
dry-data 0.4.2

class Schema < Dry::Validation::Schema::Form
  key(:email) { |email| email.filled? }
  key(:ids) do |v|
    v.array? do
      v.each(&:int?)
    end
  end
end

# works
Schema.new.call({
  "email" => "filled",
  "ids" => ["1", "2", "3"]
})

# expected to return validation error bug crashes
Schema.new.call({
  "email" => "filled",
  "ids" => "not_ids"
})

# /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/array.rb:6:in `constructor': undefined method `map' for "not_ids":String (NoMethodError)
#   from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type.rb:74:in `call'
#   from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/hash.rb:22:in `block in symbolized_constructor'
#   from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/hash.rb:18:in `each'
#   from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/hash.rb:18:in `each_with_object'
#   from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/hash.rb:18:in `symbolized_constructor'
#   from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type.rb:74:in `call'
#   from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-validation-0.4.1/lib/dry/validation/schema/form.rb:15:in `call'
#   from bug.rb:23:in `<main>'

Add ability to define your own predicates

Right now only Validation::Predicates module is the container for built-in predicates but we need a way to define our own predicates too and use them in the schema objects.

Examples are confusing

It would really help to see some examples of expected usage - not just how to define a schema, but how to consume a schema. All of the examples just show a call to #messages. Is that the intended API for determining if the input passed schema validation? If not, what is?

Also, it would be useful to include the expected output of the commands in the examples directory. The output from using those examples is very surprising to me, so I'm wondering if they are behaving as expected. For example, consider https://github.com/dryrb/dry-validation/blob/6447302f3b53766b29f29230831890a5cc3822e0/examples/form.rb

When I run that script I see:

>  errors = schema.call('email' => '', 'age' => '18').messages
# => {:age=>[["age must be greater than 18", "age must be an integer"], 18], :email=>[["email must be filled"], ""]}

So, I was curious what valid input would look like. Unfortunately, it looks very similar:

> errors = schema.call('email' => '[email protected]', 'age' => 18).messages
# => {:age=>[["age must be greater than 18", "age must be an integer"], 18]}

Is this expected behavior?

Whitelist keys

Is it possible to create a white list of keys for a hash? I'm using DryValidation instead of StrongParameters and it's sometimes fine, but in some other places I have to ensure that only whitelisted attributes are allowed to be passed. Basically that's what params[:foo].permit(:bar, :baz) does.

Expose AST for schema before call

I'd like to provide validation support for the form builder gem that I'm developing. It's easy enough just to run the form's data through a dry-validation schema after it's been posted, but that's only half of the story. The other half is providing the form builder's UI layer with information about any validation rules before they've been applied โ€“ this would allow it to display validation hints as well as perform some simple validations client-side with JS.

I know you've mentioned the idea of "validation hints" before, @solnic, and I'm guessing this is somewhat related. Do you think it might be worthwhile exposing some AST of the validation schema before it's been called with data? Then we could write an AST compiler that associates validation rule info along with each of the fields we display in our forms.

I'd be happy to have a go at exposing the AST as a first step, if you think this is a reasonable feature to support.

`Dry::Validation::Schema::Form` doesn't work with nested data

require 'bundler/setup'
require 'pry'
require 'dry-validation'
require 'dry/validation/schema/form'

class Schema < Dry::Validation::Schema
  key(:phone_numbers) do |phone_numbers|
    phone_numbers.array? do
      phone_numbers.each(&:str?)
    end
  end
end

schema = Schema.new
# works

class FormSchema < Dry::Validation::Schema::Form
  key(:phone_numbers) do |phone_numbers|
    phone_numbers.array? do
      phone_numbers.each(&:str?)
    end
  end
end

schema = FormSchema.new

# /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:34:in `visit': undefined method `visit_each' for #<Dry::Validation::InputTypeCompiler:0x007fde6d48e768> (NoMethodError)
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:47:in `block in visit_and'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:47:in `map'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:47:in `visit_and'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:34:in `visit'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:44:in `block in visit_and'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:44:in `map'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:44:in `visit_and'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:34:in `visit'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:29:in `block in call'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:29:in `map'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:29:in `call'
#   from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/schema/form.rb:11:in `initialize'
#   from bug.rb:24:in `new'
#   from bug.rb:24:in `<main>'

Extending or Inherit schema

Is there a way? Ot will it be possible to merge rules or extend? Right now inheritance overwrite all rules.

Provide access to other field values

Thanks for dry-validation! Can you provide access to others field values (e.g. value(key)) for something like this? Or is it already possible?

key(:start_date)    { |date| date.filled? & date.date? }
key(:end_date)      { |date| date.filled? & date.date? }
key(:date_of_issue) { |date| date.filled? & date.date? & date.in_period? }

def in_period?(value)
  # start_date = coerced_value(:start_date)
  # end_date   = coerced_value(:end_date)
  # date       = coerced_value(:date_of_issue)
  # value(key) returns raw value
  start_date = Date.parse(value(:start_date))
  end_date   = Date.parse(value(:end_date))      
  date       = Date.parse(value)
  (start_date..end_date).include?(value)
end

Incorrect messages for array as input

Hi @solnic ,

consider such schema:

schema = Dry::Validation.Schema do
  key(:nested).each do
      key(:name).required
      key(:age).required
  end
end

p schema.({nested: [{ name: 'Jane', age: 21 }, { name: nil, age: nil }, {name: nil, age: nil}]}).messages
  # => {:nested=>{1=>{:name=>["must be filled", "must be filled"], :age=>["must be filled", "must be  filled"]}}}

schema = Dry::Validation.Schema do
  each do
      key(:name).required
      key(:age).required
  end
end

p schema.([{ name: 'Jane', age: 21 }, { name: nil, age: nil }, {name: nil, age: nil}]).messages
  # => {1=>{:name=>["must be filled", "must be filled"], :age=>["must be filled", "must be filled"]}}

In both cases it returns probably first invalid index, but do not continue with the remaining indexes (or continues, but pushes messages into first found?).

Error messages for invalid nested data (via keys inside `hash?`) are not similarly nested

With a schema like this, with a single rule inside a single level of nesting:

schema = Dry::Validation.Schema do
  key(:meta) do
    hash? do
      key(:pages).required(:int?)
    end
  end
end

When I call it with invalid data, the error messages are not properly nested:

schema.(meta: {pages: "123"}).messages
# => {:meta=>["must be an integer"]}

FWIW, if I set the meta hash without any child keys, the error messages are properly nested:

schema.(meta: {}).messages
# => {:meta=>{:pages=>["is missing"]}}

This only seems to be a problem for keys inside a hash? block. If I use a nested schema instead:

schema = Dry::Validation.Schema do
  key(:meta).schema do
    key(:pages).required(:int?)
  end
end

Then the error messages come back properly:

schema.(meta: {pages: "123"}).messages
# => {:meta=>{:pages=>["must be an integer"]}}

nil? doesn't work

class Schema < Dry::Validation::Schema
  key(:id) { |id| id.nil? | (id.str? & id.format?(UUID)) }
end

I want to check if data hash doesn't have id or when it has then id should be uuid, so id should be in UUID format. I used str? because I checked situation when id was number too. format? method with number param raises exception.

Unfortunately when I had written above code I received:

undefined method `to_ary' for true:TrueClass

I checked backtrace and it looks:

undefined method `to_ary' for true:TrueClass
/Users/wafcio/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/dry-validation-0.1.0/lib/dry/validation/schema/key.rb:26:in `method_missing'
/Users/wafcio/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/dry-validation-0.1.0/lib/dry/validation/schema/definition.rb:6:in `key'

Checked this problem deeply and there is problem with simple nil? predicate

class Schema < Dry::Validation::Schema
  key(:id) { |id| id.nil? }
end

it returns

NoMethodError: undefined method `to_ary' for false:FalseClass
from /Users/wafcio/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/dry-validation-0.1.0/lib/dry/validation/schema/key.rb:31:in `method_missing'

Cannot instantiate form schemas

Looks like there's an issue preventing form schemas from being instantiated. I get this when I copy/paste/run your examples/form.rb code:

class UserFormSchema < Dry::Validation::Schema::Form
  key(:email) { |value| value.str? & value.filled? }

  key(:age) { |value| value.int? & value.gt?(18) }
end
[12] pry(main)> schema = UserFormSchema.new
NoMethodError: undefined method `visit_string' for #<Dry::Data::Compiler:0x007fa59d915c68 @registry=Dry::Data>
from /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:15:in `visit'

I'm using the latest from github on both dry-validation and dry-data. Full stacktrace here:

Exception: NoMethodError: undefined method `visit_string' for #<Dry::Data::Compiler:0x007fac72da9820 @registry=Dry::Data>
--
 0: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:15:in `visit'
 1: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:39:in `visit_key'
 2: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:15:in `visit'
 3: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:34:in `block in visit_hash'
 4: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:34:in `map'
 5: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:34:in `visit_hash'
 6: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:22:in `visit_type'
 7: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:15:in `visit'
 8: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:11:in `call'
 9: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-validation-fb344cc0b4d4/lib/dry/validation/input_type_compiler.rb:19:in `call'
10: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-validation-fb344cc0b4d4/lib/dry/validation/schema/form.rb:11:in `initialize'
11: (pry):8:in `new'
12: (pry):8:in `<main>'

I'm just getting started with trying out dry-validation, so I hope this is enough info for you to go on!

Add Schema::JSON

Similar to Schema::Form but with JSON-specific coercions. This will require dry-data json types, which are not done yet.

Optional keys blows up in Schema::Form

class MailboxQuery < Dry::Validation::Schema::Form
  optional(:per_page) { |pp| pp.int? & pp.gt?(0) & pp.lteq?(100) }
  key(:page) { |p| p.int? & p.gt?(0) }
end
/validation/input_type_compiler.rb:23:in `visit': undefined method `visit_implication' for #<Dry::Validation::InputTypeCompiler:0x007f9e9932b3f8> (NoMethodError)

Probably related to #16 somehow.

Coercion with optionals is broken

class PagingValidator < Dry::Validation::Schema::Form
  optional(:per_page) { |pp| pp.int? & pp.gt?(0) & pp.lteq?(100) }
  optional(:page) { |p| p.int? & p.gt?(0) }
end
result = PagingValidator.new.messages({'per_page' => '10'})
#<Dry::Validation::Schema::Result params={:per_page=>nil} errors=[[:per_page, ["per_page must be an integer"]]]>

Nesting should work for more than one level

class Schema < Dry::Validation::Schema
  key(:name) { |n| n.filled? }
  key(:data) do |data|
    data.key(:invoice_address) do |invoice_address|
      %i[street zip city country].each do |f|
        invoice_address.key(f, &:filled?)
      end
    end
end

schema = Schema.new
schema.call({
                data: {
                    invoice_address: {}
                }
            })
/vendor/bundle/bundler/gems/dry-validation-57d86bbfc390/lib/dry/validation/rule.rb:61:in `block in call': undefined method `call' for :street:Symbol (NoMethodError

Rules without type coercion should be passed through to result.params

class MySchema < Dry::Validation::Schema::Form
  key(:name)
  key(:year){ |y| y.int? & y.gt(2000) }
end

schema = MySchema.new

schema.call('name' => 'kwando', 'year' => '2016').params # => {year: 2016}

In this case I would expect the key :name to be present in the params hash and whatever value passed in with the 'name' key should just be passed through.

Currently we can work around this "issue" by always specify a type for all keys.

class MySchema < Dry::Validation::Schema::Form
  key(:name, &:str?)
  key(:year){ |y| y.int? & y.gt(2000) }
end

schema = MySchema.new

schema.call('name' => 'kwando', 'year' => '2016').params # => {name: 'kwando', year: 2016}

Optional attribute/key

Optional keys
We can assume that we have users table which has phone columns. Value for phone can be null but when it isn't then it should be valid with some regex (for example US phone format). Based on this short description these hashes should be valid:

{ name: "John Doe" }
{ name: "John Doe", phone: nil }
{ name: "John Doe", phone: "1-234-567-8901" }

but this hash shouldn't be valid:

{ name: "John Doe", phone: "abcd" }

When we define keys in schema class then each key must exists in hash. Sometime we have hash without phone key, thats why I will be very helpful to have optional keys

Another example with optional keys:
Based on JSON API in request we can have filter, sort, ... params. I want use dry-validation to validate income params. In request params optional keys will be very helpful.

{ sort: "email" }
{ filter: { kind: "member" }, sort: "name" }

It can be bypass by merge hash with defult hash structure where we have all keys but value for each key is nil.

Collection aggregation validation

What's the recommended way(if possible) to validate aggregate values on a collection? Say I want the sum of all amounts in a nested collection to equal a value of the main object, or there should be at least n items in the collection with property y.

Cannot validate nested array with just one key defined

Hi @solnic ,

while validating schema against nested array with just one key defined, e.g:

require 'bundler'
Bundler.require

schema = Dry::Validation.Form do
  key(:nested).each do
      key(:name).required
  end
end

p schema.({"nested" => [{ "name" => 'Jane', age: 21 }, { name: 'Joe', age: nil }]}).messages

it returns:

/media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:15:in `visit': undefined method `visit_n' for #<Dry::Types::Compiler:0x0000000284dd78 @registry=Dry::Types> (NoMethodError)
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:11:in `call'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:38:in `visit_form_array'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:23:in `visit_type'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:15:in `visit'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:53:in `visit_key'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:15:in `visit'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:58:in `block in merge_with'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:58:in `map'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:58:in `merge_with'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:48:in `visit_form_hash'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:23:in `visit_type'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:15:in `visit'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:11:in `call'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation/input_processor_compiler.rb:16:in `call'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation/schema.rb:104:in `input_processor'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation/schema.rb:127:in `default_options'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation/schema.rb:45:in `new'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation.rb:36:in `Schema'
        from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation.rb:41:in `Form'

However, if there's more than one key given, it spins fine.

Default i18n messages should not replace pre-existing keys

If you have an app with i18n configured, and some local i18n keys to replace dry-validation message defaults, then later requiring dry-validation will overwrite those keys.

For example, if I set up i18n like this, in the boot sequence of my app:

require "i18n"
I18n.load_path += Dir["#{MyApp::Container.root}/apps/*/locales/**/*.yml"]
I18n.backend.load_translations

And where I have a .yml file like this:

en:
  errors:
    filled?: "Fill this thing, yo"

And then later on require a file that requires dry-validation, the errors.filled? key gets replaced with the default:

[1] pry(main)> I18n.t("errors.filled?")
=> "%{name} must be filled"

If, instead, I do a little hoop-jumping and force dry-v's i18n integration to load before I suppose my own locale files, then things work as I'd want:

require "i18n"

# Load dry-validation's i18n support first, so we can override if we want
require "dry/validation/messages/i18n"

I18n.load_path += Dir["#{Alpinist::Container.root}/apps/*/locales/**/*.yml"]

I18n.backend.load_translations
[1] pry(main)> I18n.t("errors.filled?")
=> "Fill this thing, yo"

You can see these examples in action in https://github.com/icelab/alpinist/tree/sign-in-and-users-crud.

I think the preferred behaviour here would be for dry-v not to do anything that might result in pre-existing keys being overwritten.

Form schemas fail with `hash?` predicates

If I define a form schema like this:

schema = Dry::Validation.Form do
  key(:meta) do
    hash? do
      key(:pages).required(:int?)
    end
  end
end

I see this error:

NoMethodError: undefined method `visit_p' for #<Dry::Types::Compiler:0x007f9863aa0ca0 @registry=Dry::Types>
Did you mean?  visit
from /Users/tim/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/dry-types-422489afe640/lib/dry/types/compiler.rb:15:in `visit'

FWIW, it works fine if I change it to be a nested schema instead of a hash? predicate:

schema = Dry::Validation.Form do
  key(:meta).schema do
    key(:pages).required(:int?)
  end
end

Support i18-n of field names

Currently the field name is not translated in the error messages you get from calling .messages from dry-validation. This is most likely needed in order to display those errors to end users.

I'm not sure how this should work, but I think it would be good if the attribute name lookup is somewhat configurable. Sometimes you might have the data needed in the db, hardcoded in your app, in locale files etc.

Rule depending on another rule crashes

Hi @solnic !

When running validation on a rule depending on another, specifically this example, it crashes with the following exception NoMethodError: undefined method '[]' for nil:NilClass, when Schema.new is fired.

Backtrace:

        from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:77:in `visit_predicate'
        from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:18:in `visit'
        from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:106:in `visit_group'
        from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:18:in `visit'
        from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:13:in `block in call'
        from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:13:in `map'
        from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:13:in `call'
        from gems/dry-validation-0.6.0/lib/dry/validation/schema.rb:83:in `initialize'

Update

The spec/integration/rule_groups_spec.rb also fails with the same error, however switching dry-logic to v0.1.2 seems to fix the problem.

Option to yield rather than instance_eval in DSL

ISSUE

Currently the DSLs instance_eval the code block. This is fine in most cases, however it becomes awkward in certain contexts.

For example, say I want to generate an deeply nested hash validation programmatically.

The structure I'm trying to create would look like this:

    Dry::Validation.Schema do
      key(:Deeply).schema do
        key(:Nested).schema do
          key(:Object).required(&:string?)
        end
      end
    end

A failed attempt might look like this:

    # In my initializer 
     @path = %w(Deeply Nested Object)
    # ....
    Dry::Validation.Schema do
      @path.reverse.reduce(self) do |memo, segment| 
        key(:Deeply).schema
      end
    end

instance_eval changes self, so instance variables, and methods in your current object become unreachable. Therefore @path is nil (hopefully...).

  @path = [:Deeply, :Nested, :Object]  

  Dry::Validation.Schema do |schema|
     @path.reverse.reduce(schema) do |s, segment|
        s.key(segment).schema { |nested| s = nested }
        s
      end
      schema
    end

Assuming the block is optional, and the return value is the schema you just created; one solution might look like this:

  @path = [:Deeply, :Nested, :Object]  

  Dry::Validation.Schema do |schema|
     @path.reverse.reduce(schema) do |s, segment|
        s.key(segment).schema
      end
    end

I think we can implement this so both styles are possible, with changing the API. Perhaps something like this could work:

if block.arity == 1
    res = block.call(key)
else
    res = key.instance_eval(&block)
end

Validation macros

To keep the schema definitions concise, we can provide various macros to cover common use cases.

Here's an awesome API idea from @fran-worley:

class BarcodeSchema < Dry::Validation::Schema
  key(:barcode) do |barcode|
    barcode.none? | barcode.filled?
  end

  key(:job_number) do |job_number|
    job_number.none? | job_number.int?
  end

  key(:sample_number) do |sample_number|
    sample_number.none? | sample_number.int?
  end

  rule(:barcode_only) do
    rule(:barcode).filled? & (rule(:job_number).none? & rule(:sample_number).none?)
  end
end

# becomes

class BarcodeSchema < Dry::Validation::Schema
  key(:job_number).maybe(type: :int)

  key(:sample_number).maybe(type: :int)

  key(:barcode).maybe.on(:filled) do
    key(:job_number, &:none?)
    key(:sample_number, &:none?)
  end
end

High-level rules throw an exception

High-level rules described here don't seem to work in the latest version (0.6.0 ATM). Copypaste of BarcodeSchema from that page throws following error:

[4] pry(main)> BarcodeSchema.new.call({})
NoMethodError: undefined method `input' for #<Dry::Logic::Rule::Conjunction:0x0055e497bb7c28>
from /home/ineu/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-logic-0.1.4/lib/dry/logic/rule/check.rb:8:in `evaluate_input'

Document how to share schema validations

Say I have Orders and Users to validate, each of which contain an address:

{ 
  username: 1,
  address: { 
    street: '123 Main',
    city: 'Seattle'
  }
}

{ 
  product_id: 1,
  address: { 
    street: '123 Main',
    city: 'Seattle'
  }
}

it's not clear from the documentation how I could share the address validation between the two schemas.

dry-validation doesn't recognize integer in string

README has the following example:

key(:age) { |value| value.int? & value.gt?(18) }
...
errors = schema.call('email' => '', 'age' => '18').messages
...
#   :age => [["age must be greater than 18 (18 was given)", 18]]
...

As you can see 18 declared as string value but validation recognize it as integer value and validate it. Seems like this is don't work now (don't recognize integer value in string and raise error: age must be an integer).

Is it bug in validation or README?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.