Git Product home page Git Product logo

dry-schema's People

Contributors

actions-user avatar beechnut avatar bmalinconico avatar byroot avatar drenmi avatar dry-bot avatar flash-gordon avatar ianks avatar igor-alexandrov avatar mensfeld avatar mlisivka avatar morozzzko avatar ojab avatar olimart avatar olleolleolle avatar radarek avatar rindek avatar robhanlon22 avatar segiddins avatar skryukov avatar solnic avatar svenanderzen avatar svobom57 avatar tiev avatar timjnh avatar timriley avatar tomdalling avatar tomgi avatar tylerhunt avatar weppos avatar

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

dry-schema's Issues

Return empty list for predicates without errors

Result#errors and sibling methods are returning a list when there are errors on the predicate given as key or nil when there is none.

When there is no items for a list it makes more sense to return the empty list instead of nil, this way you don't have to ask for the type in your code:

# Now
result.errors.map(&:html_for_errors) unless result.errors.nil?

# With the proposal
result.errors.map(&:html_for_errors)

I would happily try a PR if you like the idea.

change keys after validation

I'd like to have the ability to change a key for the success case, to that:

params = {address: {city: "London"}}
result = schema.call(params)
result.output => {"address_attributes" => {"city" => "London" }}

As you might have guessed, I'm trying to make active record nested assignment happen :)

Custom error namespace for custom schemas like JSON and Form

It would be nice if Dry::Validation.JSON and Dry::Validation.Form had their own error message namespace. Something like this:

en:
  schema:
    errors: &default
      array?: "must be an array"

      empty?: "must be empty"

      # ...

      hash?: "must be a hash"

      # ...

  json:
    errors:
      <<: *default

      hash?: "must be an object"

This way, when you build a validation using one of this domain specific schemas you would get domain specific messages:

DEFINITION = proc { required('thing').value(:hash?, :empty?) }

json_schema   = Dry::Validation.JSON(&DEFINITION)
normal_schema = Dry::Validation.Schema(&DEFINITION)

input = { 'thing' => [] }

normal_schema.call(input).messages # => {"thing"=>["must be a hash", "must be empty"]}
json_schema.call(input).messages   # => {"thing"=>["must be an object", "must be empty"]}

High level rules inside Arrays don't return the index on the error message

Ex:
{:should_have_investment_rate_only=>["must be filled", "must be filled", "must be filled", "cannot be defined"], 2=>{:minimum_investment=>["is missing"]}}

The rule :should_have_investment_rate_only return an array of error messages, but it's not possible to know if it was on item 1,2 or 42. It would be better if it worked like the other rules.

The schema was something like:

BATCH_SCHEMA = Dry::Validation.JSON do
  each do
    schema(OTHER_SCHEMA)
  end
end

OTHER_SCHEMA = Dry::Validation.JSON do
  required(:minimum_investment) #blah blah

  rule(:should_have_investment_rate_only) do
    # the rule
  end
end

Namespace the i18n keys

I know this was somewhat discussed before, and there are probably not many people who are bothered by this, but I figured I would post here anyway.

The fact that you are adding i18n keys to the already existing errors namespace causes problems for me. As far as I could see in other gems - for example devise - it is common for a gem to put their i18n keys under their own root namespace.

Also - to give a little context - I should mention that I am not using dry-validations at all, and the gem was installed indirectly for me by the config gem - so from my point of view, its a "feature" I am not using, and is causing some issues in my codebase, and I have no way of removing, other than to stop using the config gem.

I guess my suggestion is - if at all possible - is to namespace your i18n keys under some dry_validations: root node.

MessageCompiler fetches messages using wrong locale

This can be reproduced when used with config.messages = :i18n, may also apply to custom messages.

Minimal reproducible example

begin
  require 'bundler/inline'
rescue LoadError => e
  $stderr.puts 'Bundler version 1.10 or later is required'
  raise e
end

gemfile(true) do
  source 'https://rubygems.org'

  gem 'dry-validation', '0.11.1', require: false
  gem 'i18n', '0.8.6', require: false
  gem 'rspec', require: 'rspec/autorun'
end

require 'i18n'
require 'dry-validation'

UserForm = Dry::Validation.Schema do
  configure do
    config.messages = :i18n
    config.namespace = :user
  end

  required(:name).filled(:str?)
end

I18n.available_locales = %i[en ru]
I18n.locale = I18n.default_locale = :en
I18n.backend.store_translations(
  :en,
  errors: {
    rules: {
      user: {
        rules: {
          name: {
            filled?: 'must be filled'
          }
        }
      }
    }
  }
)
I18n.backend.store_translations(
  :ru,
  errors: {
    rules: {
      user: {
        rules: {
          name: {
            filled?: 'заполните это поле'
          }
        }
      }
    }
  }
)

RSpec.describe 'I18n validation messages' do
  subject(:result) { UserForm.call(name: nil) }

  around do |example|
    # Set Russian locale for all examples
    I18n.with_locale(:ru) { example.run }
  end

  EXPECTED = ['заполните это поле'].freeze

  context 'with new implicit options ({ hints: false })' do
    it { expect(result.errors[:name]).to eq(EXPECTED) }
  end

  context 'with explicit locale' do
    it { expect(result.messages(locale: I18n.locale)[:name]).to eq(EXPECTED) }
  end

  # Failing example!
  context 'when reusing default MessageCompiler instance' do
    it { expect(result.messages[:name]).to eq(EXPECTED) }
  end
end

# Monkey patch MessageCompiler to make the failing example pass:
#
# module PatchMessageCompiler
#   def default_lookup_options
#     { locale: messages.default_locale }
#   end
# end
#
# Dry::Validation::MessageCompiler.prepend PatchMessageCompiler

Expected behaviour

All tests pass.

Actual behaviour

One test fails.

Why?

After some debugging I've found out that messages are fetched in MessageCompiler#visit_predicate:

https://github.com/dry-rb/dry-validation/blob/806ce1adc14d4bd340d8d31f3c89d6c433f12303/lib/dry/validation/message_compiler.rb#L107-L113

msg_opts is a hash with options passed to I18n.t. It contains the :locale option deduced from default_lookup_options, which is set in the constructor using messages.default_locale (active locale at the moment):

https://github.com/dry-rb/dry-validation/blob/806ce1adc14d4bd340d8d31f3c89d6c433f12303/lib/dry/validation/message_compiler.rb#L13-L20

MessageCompiler is instantiated when Schema subclass is created. It can be instantiated one more time when validation error messages are retrieved via MessageCompiler#with, but only when provided with non-default (non-empty) options, otherwise the first created instance of MessageCompiler is reused:

https://github.com/dry-rb/dry-validation/blob/806ce1adc14d4bd340d8d31f3c89d6c433f12303/lib/dry/validation/message_compiler.rb#L30-L33

So we have the following problem:

  1. MessageCompiler is instantiated when the schema subclass is created with the locale which was active at that moment;
  2. The current locale is changed;
  3. Schema is called to validate the input, validation error messages are retrieved using the Result#messages method (with empty options hash);
  4. The first created instance of MessageCompiler is reused, so we get messages in the locale which was active when that instance was created, not in the current locale.

If some options are passed to Result#messages explicitly or implicitly (Result#errors passes { hints: false }, Result#hints passes { failures: false }), then MessageCompiler#with returns new instance that has the current locale as the :locale options in default_lookup_options, so we get messages in the current locale.

Possible workarounds

  • Make #default_lookup_options fetch locale each time it's invoked (see how I monkey-patched MessageCompiler in the example above).

  • Change MessageCompiler#with so it takes the current value of messages.default_locale into account when deciding whether to create a new instance:

    def with(new_options)
      return self if new_options.empty? && locale == messages.default_locale
      self.class.new(messages, options.merge(new_options))
    end
  • Don't pass :locale option to Messages::Abstract#call?

Invalid custom error messages for duplicate keys in multiple namespaces

Hi guys, I've encountered a strange behavior in custom error messages for duplicate keys within different namespaces. Example:

I'm having 2 schemas:

# test.rb

student = Dry::Validation.Schema do
  configure do
    config.messages_file = './errors.yml'
    config.namespace = :student
  end

  required(:name).filled
end

teacher = Dry::Validation.Schema do
  configure do
    config.messages_file = './errors.yml'
    config.namespace = :teacher
  end

  required(:name).filled
end

And an errors.yml file with custom error messages:

en:
  errors:
    rules:
      student:
        rules:
          name:
            filled?: Student - name is missing
      teacher:
        rules:
          name:
            filled?: Teacher - name is missing

It seems as if the order in which schemas are called has an effect on the output of error messages. See: run the test.rb script (with a breakpoint after schemas definitions):

[1] pry(main)> teacher.call(name: "").messages
=> {:name=>["Teacher - name is missing"]}
[2] pry(main)> student.call(name: "").messages
=> {:name=>["Teacher - name is missing"]}

Run the script again and call schemas in different order:

[1] pry(main)> student.call(name: "").messages
=> {:name=>["Student - name is missing"]}
[2] pry(main)> teacher.call(name: "").messages
=> {:name=>["Student - name is missing"]}

After calling the first schema an error message for subsequent schemas with duplicate key is repeated (as if it would be cached somewhere) - namespace is ignored. I'm not sure whether it is a bug or I'm just defining my schemas/error messages file in incorrect way. Has anyone encountered the same problem?

Translation for schema keys

Maybe this is a dumb question but I really didn't find it anywhere in the documentation.

How can I translate a Schema field name? Example:

UserValidation = Dry::Validation.Schema do
  configure { config.messages = :i18n }

  required(:age).filled
end

When I want the full messages i got the message translated correctly, but not the field name.
age obrigatório(a). I didn't find the translation key for 'age', which should be 'idade' of the translated name.

Maybe the yml file should have something like this?

pt_BR:
  errors:
    fields:
      age: Idade

Sorry about my english. To be clearer I want to know the translation key to be able to translate the Schema fields. If I have required(:name).filled I want to setup my i18n yml file to have 'name' translated in the full messages validations.

Thanks for everything and congratulations for the excelent lib! 👍

PredicateInferrer doesn't handle enum types

Dry::Schema::PredicateInferrer[Dry::Types["strict.string"].enum("foo", "bar")]
Traceback (most recent call last):
       10: from ./bin/console:16:in `<main>'
        9: from (irb):1:in `<main>'
        8: from /Users/tim/Source/github.com/dry-rb/dry-schema/lib/dry/schema/predicate_inferrer.rb:79:in `[]'
        7: from /Users/tim/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/dry-core-0.4.7/lib/dry/core/cache.rb:49:in `fetch_or_store'
        6: from /Users/tim/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:192:in `fetch_or_store'
        5: from /Users/tim/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:172:in `fetch'
        4: from /Users/tim/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:193:in `block in fetch_or_store'
        3: from /Users/tim/Source/github.com/dry-rb/dry-schema/lib/dry/schema/predicate_inferrer.rb:80:in `block in []'
        2: from /Users/tim/Source/github.com/dry-rb/dry-schema/lib/dry/schema/predicate_inferrer.rb:30:in `visit'
        1: from /Users/tim/Source/github.com/dry-rb/dry-schema/lib/dry/schema/predicate_inferrer.rb:30:in `public_send'
NoMethodError (undefined method `visit_enum' for #<Dry::Schema::PredicateInferrer::Compiler:0x00007fa821348c50>)
Did you mean?  visit_sum

Explicitly mark that a param can be ignored if nil

Given there's a search form, where all fields are optional, and a validation schema that receives the form raw data via rack.

As we know, the form will be submitted with empty strings as values for empty fields.

Extremely simplified, the params could look something like:

Params = Dry::Validation.Form do
  optional(:date).maybe(:date?) # or required, as it's a form, it will be set anyway
end

So, when the form submits "" for the date param, the validation output will look like { date: nil }, and we have to handle this nil value somewhere.

Is there any way to explicitly say that this param can be ignored if nil? Like "ok, I know it can be empty or nil, but if it is, please just don't put it in the output", so that the output would look like {} and we'd not have to handle the nil value elsewhere.

Can't redefine reused schemas

require 'dry-validation'

Data = Dry::Validation.Schema do
  required(:age).filled(:number?)
end

ChangedData = Dry::Validation.Schema(Data) do
  required(:age).filled(:number?, gteq?: 18)
end

Form = Dry::Validation.Schema do
  required(:user).schema(ChangedData)
end

BadForm = Dry::Validation.Schema do
  required(:user).schema(Data) do
    required(:age).filled(:number?, gteq?: 18)
  end
end

p Form.call(user: { age: 15 })
#<Dry::Validation::Result output={:user=>{:age=>15}} errors={:user=>{:age=>["must be greater than or equal to 18"]}}>
p BadForm.call(user: { age: 15 })
#<Dry::Validation::Result output={:user=>{:age=>15}} errors={}>

Namespaced rule name translations cannot be resolved

If I set namespace for my schema and use full messages option, this will not get translated:

Schema = Dry::Validation.Params do
  configure do
    config.namespace = :my_namespace
  end

  required(:my_rule).filled
end

en:
  rules:
    my_namespace:
       my_rule: Rule translated

Schema.(my_rule: '').messages(full: true) # should output 'Rule translated must be filled'

On the other hand, if i didn't set namespace it will work as intended:

en:
  rules:
    my_rule: Rule translated

I'm not sure if it's a bug or but looks like one.

Simpler way of inheriting rules from multiple schemas

see spec/schema/inheriting_schema_spec.rb ....

Situation : I have a number of fields that are shared throughout my application which are not nested schema - but i would like to define a single-field validation schema for each and include as needed in other schema ... the schema-inheritance works for nested (compound) hashes - see above. can we also support for non-nested... here's the above spec with 'flat' field added which fails ...

require 'dry-validation'

RSpec.describe 'Reusing schemas' do
  subject(:schema) do
    Dry::Validation.Schema do
      required(:city).filled

      required(:location).schema(LocationSchema)
      required(:country).schema(CountrySchema)
    end
  end

  before do

    LocationSchema = Dry::Validation.Schema do
      configure { config.input_processor = :form }

      required(:lat).filled(:float?)
      required(:lng).filled(:float?)
    end

    # define a 'flat' (scalar) schema

    CountrySchema = Dry::Validation.Schema do
      configure { config.input_processor = :form }
      required(:country).filled(:str?)
    end

  end

  after do
    Object.send(:remove_const, :LocationSchema)
  end

  it 're-uses existing schema' do
    expect(schema.(city: 'NYC', country: 'usa', location: { lat: 1.23, lng: 45.6 })).to be_success

    expect(schema.(city: 'NYC', country: 'usa', location: { lat: nil, lng: '45.6' }).messages).to eql(
      location: {
        lat: ['must be filled'],
        lng: ['must be a float']
      }
    )
  end
end

Thanks !

.hints throws an error on empty input

I encounter an issue when trying out the library to validate Rails controller params.

module ParamSchemas
  UserSchema = Dry::Validation.Form do
    required(:user).schema do
      required(:name).filled(:str?)
      required(:age).filled(:int?, gt?: 18)
    end
  end
end

http_params = {}
params = ActionController::Parameters.new(http_params)
result = ParamSchemas::UserSchema.(params.to_unsafe_h)
result.hints

=> TypeError: no implicit conversion of Symbol into Integer
from /Users/viett/.rvm/gems/ruby-2.4.1/gems/dry-validation-0.11.1/lib/dry/validation/message_set.rb:85:in `[]'

This only happens when http_params is an empty hash.
When http_params = { user: '' }, things work fine.

Rules from enum types aren't checked

Enum validation from dry-types doesn't seem to work with type_specs

require 'dry-types'
require 'dry-validation'

State = Dry::Types['strict.string'].enum('enabled', 'disabled')

schema = Dry::Validation.Form do
  configure { config.type_specs = true }

  required(:state, State).filled
end

schema.(state: "enabled").errors
=> {}
schema.(state: "foo").errors
=> {:state=>["must be one of: enabled, disabled"]}

Please note that It works as described in: dry-rb/dry-validation#348

for example required(:state).filled(State) makes the above example work properly

Options with default values

I'm trying to use this lib for schema validation, and there is this use case where a key needs to be filled with a default, when nothing is passed.

I know that this wasn't in the scope of dry-validation, but that this is dry-schema, maybe the use case now fits.

Something like:

optional(:op_type).default("read")

Retrieve validation messages throw ArgumentError when `$DEBUG` is true

Hi,

This problem is because ruby throws "too many arguments for format string" when $DEBUG is true and placeholders and arguments in string templates are not matched.

I was on dry-validation-0.10.5

Below, I put together a code snippet that would reproduce the behaviour. In this particular case demonstrated by my code snippet, it failed at the beginning of message_complier.rb#message_text when the template was 'is missing' and the tokens were { input: null } .

I ran into this when running hanami in debug, and this would halt it.

I just wonder if this is an improvement opportunity.

Many thanks,

mytram

require 'dry-validation'

$DEBUG = true

schema = Dry::Validation.Schema do
  required(:name).filled
end

result = schema.call(
  email: '[email protected]',
  address: { street: 'Street 1', city: 'NYC', zipcode: '1234' }
)

# this will throw ArguementError as $DEBUG is on
puts "error message: #{result.messages.inspect}"

# .rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/dry-validation-0.10.5/lib/dry/validation/message_compiler.rb:163:in `%': too many arguments for format string (ArgumentError)

PredicateInferrer chokes on certain types

A few cases I found during re-writing my dry-v schemas to dry-schema

Safe type

Dry::Schema::PredicateInferrer[Dry::Types['params.bool']]
# NoMethodError (undefined method `primitive' for #<Dry::Types::Safe:0x00007fdd1f059ea8>)

Sum type with constructor

non_empty_string = Dry::Types['string'].optional.constructor { |str| str.empty? ? nil : str }
Dry::Schema::PredicateInferrer[non_empty_string]
# NoMethodError (undefined method `primitive' for #<Dry::Types::Sum:0x00007fdd1f0ec3c0>)

Make JSON schema resilient to any JSON-compatible value as input

The JSON standard defines the following data types

  • number (WRT ruby both float and integer shall be supported)
  • string
  • boolean
  • array
  • object (represented as Hash in ruby)
  • null (= nil)

If any JSON schema receives any value of the types above it shouldn't raise an exception at least. Better yet it should provide a helpful error message.

Optional keys declared missing

I have a schema with an optional key w/o bounds, dry-schema complains when it's not passed

Dry::Schema.define { optional(:value) }.({})
#  => #<Dry::Schema::Result{} errors={:value=>["is missing"]}>

whereas adding any specification changes this behavior

Dry::Schema.define { optional(:value).filled }.({})
# => #<Dry::Schema::Result{} errors={}>

I would expect optional(:value) to be enough in this case.

Why not support ruby 2.3?

The same could be said of ruby 2.2 .

For context: I'm in the process of porting an outdated dry-validation integration to dry-schema. I tried first with 0.1.1, but ran into the bug related with the error messages not being consistent with nested schemas. I've seen the changelog mentions for 0.2.0, and wanted to give it a try, but this project runs ruby 2.3, so no can do.

Now, migrating to ruby 2.4 is way less trivial than migrating our schema validators (it's a fairly big app). And I don't know exactly what features depend on ruby 2.4 tbh. Are there any? Because unless these fixes are backported to 0.1.x, I'll be stuck with unsupported dry-validation until we migrate to 2.4, which might take some time. :/

Broken when warnings enabled

When ruby runs with warnings enabled, the strangest thing happens, following warnings show up:

/Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema.rb:17: warning: in `define': the last argument was passed as a single Hash
/Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema.rb:27: warning: although a splat keyword arguments here

then, when trying to apply a schema and errors are being generated, it crashes with SystemStackError:

     SystemStackError:
       stack level too deep
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:179:in `each_with_object'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:179:in `message_tokens'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:106:in `visit_predicate'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `block in visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `block in call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:142:in `message_set'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:106:in `errors'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-equalizer-0.2.1/lib/dry/equalizer.rb:76:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-equalizer-0.2.1/lib/dry/equalizer.rb:76:in `block in define_hash_method'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:78:in `hash'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:78:in `hash'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:78:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:116:in `visit_predicate'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `block in visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `block in call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:142:in `message_set'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:106:in `errors'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:151:in `inspect'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `inspect'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `reject'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `lookup'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:79:in `block in call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:193:in `block in fetch_or_store'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:172:in `fetch'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:192:in `fetch_or_store'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:78:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:116:in `visit_predicate'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `block in visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `block in call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:142:in `message_set'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:106:in `errors'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:151:in `inspect'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `inspect'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `reject'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `lookup'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:79:in `block in call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:193:in `block in fetch_or_store'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:172:in `fetch'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:192:in `fetch_or_store'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:78:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:116:in `visit_predicate'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `block in visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `block in call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:142:in `message_set'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:106:in `errors'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:151:in `inspect'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `inspect'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `reject'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:99:in `lookup'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:79:in `block in call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:193:in `block in fetch_or_store'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:172:in `fetch'
     # /Users/solnic/.gem/ruby/2.6.1/gems/concurrent-ruby-1.1.4/lib/concurrent/map.rb:192:in `fetch_or_store'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/messages/abstract.rb:78:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:116:in `visit_predicate'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `block in visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:79:in `visit_and'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:50:in `visit'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `block in call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `map'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/message_compiler.rb:45:in `call'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:142:in `message_set'
     # /Users/solnic/.gem/ruby/2.6.1/gems/dry-schema-0.1.0/lib/dry/schema/result.rb:106:in `errors'

Deeply nested namespaced messages

en:
  errors:
    rules:
      email:
        filled?: "the email is missing"

      user:
        filled?: "name cannot be blank"

        rules:
          address:
            filled?: "You gotta tell us where you live"

Please example for more nested rule message.

each doesn't accept type specs

Dry::Schema.Params { required(:foo).value(:array).each(Types::Params::Integer) }
rule_compiler.rb:23:in `visit': undefined method `visit_constructor' for #<Dry::Schema::Compiler:0x00007fcd389d4b50>

throws an error as does

Dry::Schema.Params { required(:foo).value(:array).each { value(Types::Strict::Integer) } }

We likely want to support both options

Input preprocessing doesn't work

Here is a sample code that demonstrates the problem:

module T
  include Dry::Types.module
  Foo = T::String
end

s = Dry::Schema.build do
      configure do
        config.type_specs = true
        config.input_processor = :sanitizer
      end
      required(:foo, T::Foo).filled
      required(:bar).filled
    end
  end;
result = s.({bar: '1', foo: '1'})

Result:

=>  #<Dry::Schema::Result output={:foo=>"1"} errors={:bar=>["is missing"]}>

Expected

=>  #<Dry::Schema::Result output={:foo=>"1", :bar=> "1"} errors={}>

Using optional gives an invalid error message

This schema

Schema = Dry::Schema.define do
  optional(:contacts).array do
    hash do
      required(:name).filled
    end
  end
end

produces an invalid error message

2.6.1 :001 > Schema.(contacts: [{ }])
 => #<Dry::Schema::Result{:contacts=>[{}]} errors={:contacts=>{0=>{:contacts=>["is missing"]}}}>
# ^see contacts => contacts

At the same time changing optional(:contacts).array to required(:contacts).array gives the expected result:

2.6.1 :001 > Schema.(contacts: [{ }])
 => #<Dry::Schema::Result{:contacts=>[{}]} errors={:contacts=>{0=>{:name=>["is missing"]}}}> 

Inheriting multiple schemas

I'm having a problem with inheriting multiple schemas at once

Here is an example of what I mean

Sortable = Dry::Validation.Params do
  optional(:sort).filled
end
Findable = Dry::Validation.Params do
  required(:id).filled(:int?, gt?: 0)
end
Paginatable = Dry::Validation.Params do
  optional(:page).schema do
    required(:number).filled(:int?, gt?: 0)
    required(:size).filled(:int?, gt?: 0)
  end
end
IndexSchema = Dry::Validation.Params(
  ::Schemas::Concerns::Paginatable,
  ::Schemas::Concerns::Sortable,
  ::Schemas::Concerns::Findable
) do
  optional(:filter).schema do
    optional(:email).filled(:str?)
    optional(:fullname).filled(:str?)
  end
end

The problem comes from here as Params can have only one base.

Can't validate unknown keys in parameters

Say I have the following schema:

{
  content_type: {
    name: "my content type",
    field_schema: {
      author: { field_type: "StringField", settings: { max_length: 100 } },
      some_other_generic_field_name: { field_type: "StringField", settings: { } }
    }
  }
}

I would expect to somehow be able to validate this:

required(:content_type).schema do
  required(:name).filled(:str?)
  required(:field_schema).schema do
    each_unknown_key_pair do
      required(:field_type).filled(:str?)
      required(:settings).filled(:hash?)
    end
  end
end

(I was trying to do this in Hanami)

But, as you are aware, there is no DSL for each_unknown_key_pair. I've tried using each but this expects an Array.. so maybe we could use each_pair, or something similar, which would work for a hash where we don't care about or know the key but just want to validate the values?

@solnic requested that I open this issue. Please let me know if I can add any more information!

Type specs aren't applied when referencing another schema

Form = Dry::Schema.Params do
  required(:bar).type(:integer).value(:int?)
end

FormContainer = Dry::Schema.Params do
  optional(:foo).value(:array).each(Form)
end

puts FormContainer.(foo: [{ bar: '123' }]).inspect

Expected:

#<Dry::Schema::Result{:foo=>[{:bar=>123}]} errors={}>

Actual:

#<Dry::Schema::Result{:foo=>[{:bar=>\"123\"}]} errors={:foo=>{0=>{:bar=>[\"must be an integer\"]}}}>

i18n message not work well when change I18n.locale after define the schema

I create a gist to explain what I mean
https://gist.github.com/niedhui/87b341fd17e9bfdfbbe2aa8ca69bd0eb
Messages::I18n#get always had a locale options, the value is
the I18n.locale when the schema is defined. It does not consider the current I18n.locale(eg, change the I18n.locale after the schema defination), which Messages::I18n#key? does.
I know I can pass the {locale: I18n.locale} option explicitly to the Result#messages, but it seems not the way how the I18n gem works

Option to return `nil` values for absent keys

Dry::Struct requires any attribute to be defined in an input hash, even if it's optional and with default value.

While it's logical behaviour, it may be not ok for different cases - for example for public API.

There are some API with optional attributes, that may change. And necessity to update all clients code every time API adds new optional field isn't desirable, even sometimes impractical.

Thus, such API should contain code logic to fulfil missed elements in hash with nil, which may be hard, especially in nested data structures. It's a boilerplate code, often ugly.

So it'd be helpful to have an option to make dry-validation schema return all keys in output hash with nil value, even there wasn't such key in input hash. Schema already has every needed attribute defined, with correspondent type, so IMHO it's good place to put such functionality in.

It may be on more input_processor (however I saw input processor is in deprecated.rb file).

Schema with dynamic keys

Hi,

Are there any options to describe schema with dynamic keys?

Let's say I have a hash where keys are string numbers:

{
  foo: {
    '1' => { name: 'bar1' },
    '2' => { name: 'bar2' },
     ...
    '100' => { name: 'bar100' }
  }
}

For now I can write a schema by using some limited predefined keys:

Dry::Validation.Schema do
  required(:foo).schema do
    ('1'..'100').each do |position|
      optional(position).schema do
        required(:name).filled(:str?)
      end
    end
  end
end

But this approach is not that flexible in case if I need to accept keys which may be any number.

Is using imperative custom validation blocks the only option to write flexible validation in this case?

BarSchema = Dry::Validation.Schema do
  required(:name).filled(:str?)
end

schema = Dry::Validation.Schema do
  required(:foo).filled

  configure do
    def self.messages
      # How can I get detailed errors here from BarSchema?
      super.merge(en: { errors: { valid_foo: 'foo is not valid' } })
    end
  end

  validate(valid_foo: :foo) do |key_values|
    key_values.all? do |k, v|
      k ~= /\A\d+\z/ && BarSchema.call(v).success?
    end
  end
end

Key coercing in Each works only if you set array type spec

This form schema has .each right after required

schema = Dry::Schema.Form do
  required(:foo).each do
    schema do
      required(:bar).filled(:string)
    end
  end
end

and it rejects valid input

2.6.1 :001 > schema.("foo" => ["bar" => "baz"])
 => #<Dry::Schema::Result{:foo=>[{"bar"=>"baz"}]} errors={:foo=>{0=>{:bar=>["is missing"]}}}> 

At the same time

schema = Dry::Schema.Form do
  required(:foo).value(:array).each do
    schema do
      required(:bar).filled(:string)
    end
  end
end

works 👌

2.6.1 :001 > schema.("foo" => ["bar" => "baz"])
 => #<Dry::Schema::Result{:foo=>[{:bar=>"baz"}]} errors={}> 

The fact it silently doesn't work is surprising.

Same locale namespace for nested schemas with different configuration

Let's say I have two schemas:

CommentSchema = Dry::Validation.Schema(ApplicationSchema) do
  configure do
    config.namespace = :comment
  end

  required(:comment_body).filled
end

PostSchema = Dry::Validation.Schema(ApplicationSchema) do
  configure do
    config.namespace = :post
  end

  required(:post_body).filled
  required(:comment).schema(CommentSchema)
end

And I have the following lines in my en.yml:

en:
  errors:
    rules:
      comment:
        rules:
          comment_body:
            filled?: "COMMENT can't be blank"

      post:
        rules:
          post_body:
            filled?: "POST can't be blank"

When I call them independently error messages are correct:

PostSchema.call(post_body: '').errors
# =>  {:post_body=>["POST can't be blank"], :comment=>["is missing"]}
CommentSchema.call(comment_body: '').errors
# => {:comment_body=>["COMMENT can't be blank"]}

But when I put comment parameters inside post parameters dry-validation uses only default errors for nested parameters:

PostSchema.call(post_body: '', comment: { comment_body: '' })
{:post_body=>["POST can't be blank"], :comment=>{:comment_body=>["must be filled"]}}

On my project we have a very noisy i18n backend, here's the list of the keys that dry-validation checks - https://gist.github.com/iliabylich/6c0db70a547fb4ba5d14c6f3d1d5ac36. As you may see it tries to get keys like errors.rules.post.comment_body.failure (which means that it ignores configured config.namespace for nested schema)

First of all, is it a bug or misconfiguration? Does dry-validation support it?

array macro doesn't work in Value's DSL

What I tried to do

Schema = Dry::Schema.define do
  required(:foo).value do
    hash { required(:bar).value(:string) } |
    array { hash { required(:baz).value(:string) } }
  end
end

What it said

undefined method `array' for #<Dry::Schema::Trace:0x00007ff14b8da970> 

Generating errors for unexpected keys in Hash

I would love to be able to define a schema which generated errors if it was given unexpected keys:

require 'dry/validation'

schema = Dry::Validation.Schema(strict: true) do
  hash? do
    required(:foo).filled(:int?)
    required(:bar).filled(:str?)
  end
end

outcome = schema.call(foo: 1, bar: "two", baz: 3.0)
p outcome.messages # => {:baz=>["is not allowed"]}

This is really important for defining a public JSON API where you want to reject invalid user input.

Built-in predicate names

The names chosen for some of the built-in predicates don't match those commonly used in other languages, increasing cognitive load and the potential for user error. Please consider using the common names instead:

dry common
eql eq
gt gt
gteq ge
lt lt
lteq le

Types::Hash.map fail to validate

The dry-struct extension fails when the struct includes a Types::Hash.map attribute.

undefined method `visit_map' for #<Dry::Validation::InputProcessorCompiler::Params:0x00007f84448ab0e0> (NoMethodError)

Add struct extension

I assume that this is also in the works, but it's the only issue barring early adoption.

How to get list of required fields, optional fields and all fields?

Hi,

For example I have schema:

class ParamsValidator < Dry::Validation::Schema::Form
  key(:a) { ... }
  optional(:b) { ... } 
end

How can I get list of required fields ([:a]), optional fields ([:b]) and all fields ([:a, :b])?

FYI: I want to use these lists in unit-tests for check validation of optional fields.

Message generator chokes on processing disjunction

Taken from dry-rb/dry-validation#280

(Dry::Schema.define { required(:foo) { str? & filled? | int? } }).call({}).messages

Traceback (most recent call last):
        17: from ./bin/console:40:in `<main>'
        16: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/result.rb:117:in `messages'
        15: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/result.rb:142:in `message_set'
        14: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:45:in `call'
        13: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:45:in `map'
        12: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:45:in `block in call'
        11: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:50:in `visit'
        10: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:79:in `visit_and'
         9: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:79:in `map'
         8: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:79:in `block in visit_and'
         7: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:50:in `visit'
         6: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:62:in `visit_hint'
         5: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:50:in `visit'
         4: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:137:in `visit_key'
         3: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:50:in `visit'
         2: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:93:in `visit_or'
         1: from /Users/gordon/dev/dry-rb/dry-schema/lib/dry/schema/message_compiler.rb:93:in `new'

Short way of building optional types

Things like [:nil, :string] can lead to confusion when we're dealing with coercible types. Changing the order changes the semantics though for sum types, in general, the semantics remains intact. I think we could have a means for building optional types in a similar way do it for arrays atm:

optional(:foos).value(array[:integer])
optional(:bar).value(optional[:string])

The biggest problem here is coming up with naming 😟 Overloading optional doesn't look nice.

Make schemas quack like procs/lambdas

At the moment you use schemas like:

    ClientRequest = Dry::Schema.Params do
      required(:request_id) { filled? > str? }
      required(:client_logid).filled(:string)
      required(:name).filled(:string)
      required(:currency).filled(:string)
    end

  ClientRequest.call(client_logid: 'foo', name: 'Foo', currency: 'EUR')

if you were to mix in something like this into Dry::Schema.Params:

module Functional
  def method_call
    method(:call)
  end

  delegate :>>, :<<, to: :method_call

  alias_method :as_proc, :method_call

  def [] *args, &block
    call(*args, &block)
  end
end

you would get full inter-operability with the "ruby lambda ecosystem", ie:

  1. use the same short syntax for calling schemas like you do with lambdas ClientRequest[client_logid: 'foo', name: 'Foo', currency: 'EUR']
  2. you would get compatibility with rxruby and similar functional libraries
  3. you would be able to use Ruby 2.6 function composition ie: (UserValidator >> ApiValidator >> SomeProcOrMethodDoingValidating)[request]
  4. ...

I have been using "functional" mixins with "single public method classes" in my code for years now, I have only good things to say about it and the flexibility it allows you to play with and mix components 👍

Improved reusing of schemas with class inheritance

This is a nice library and these features would make it ever nicer.

Ability to add new fields when reusing schema. Here, the EmailAddress::Schema::Create class is reused in another schema with the addition of a new :email_address_id to the nested hash.

class EmailAddress::Create < ApplicationSchema
  define! do
    required(:email_address, Types::Email).value(:str?)
    optional(:email_label, Types::String).maybe(:str?)
    optional(:email_primary, Types::Bool).value(:bool?)
  end
end

class EmailAddress::Update < ApplicationSchema
  define! do
    required(:email_address).schema(EmailAddress::Create) do
      required(:email_address_id, Types::String).value(:str?)
    end
  end
end

The above would be equivalent to

class EmailAddress::Update < ApplicationSchema
  define! do
    required(:email_address).schema do
      required(:email_address, Types::Email).value(:str?)
      optional(:email_label, Types::String).maybe(:str?)
      optional(:email_primary, Types::Bool).value(:bool?)
      required(:email_address_id, Types::String).value(:str?)
    end
  end
end

The ability to reuse a schema in a flat / non-nested manner (issue #204) following conventions already established, like so:

schema(EmailAddress::Schema::Create)
required(:email_address_id, Types::String).value(:str?)

The above would be equivalent to adding:

required(:email_address, Types::Email).value(:str?)
optional(:email_label, Types::String).maybe(:str?)
optional(:email_primary, Types::Bool).value(:bool?)
required(:email_address_id, Types::String).value(:str?)

When using class inheritance to define a schema class, the ability to override inherited schema methods in a child class. Like so

class EmailAddress::Schema::Base < ApplicationSchema
  configure 
    def valid_primaries?(email_addresses)
      true # < -- change
    end
  end

  define! do
    required(:email_address, Types::Email).value(:valid_primaries?)
    optional(:email_label, Types::String).maybe(:str?)
    optional(:email_primary, Types::Bool).value(:bool?)
  end
end

class EmailAddress::Schema::Create < EmailAddress::Schema::Base
  configure do
    def valid_primaries?(email_addresses)
      false # < -- change
    end
  end

  define! do
    required(:email_addresses, Types::Array).filled.each do
      schema(EmailAddress::Schema::Create)
    end
  end
end

In the above, it'd be awesome if EmailAddress::Schema::Create's updated "valid_primaries?" function was used for the inherited required(:email_address, Types::Email).value(:valid_primaries?). When I tried to do something like this, Dry-validation generated an error.

Thanks! Great library!

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.