Namespaced error messages fail in a weird way

Hi all,

In my project, I get this weird crash after I put a config.messages.namespace on one of my Contracts.

It’s weird, b/c I cannot put together a minimal failing script. The script I came up with below is just doing what it’s supposed to. Even when I run it in a console with my entire project loaded (I’m not doing any auto loading)

Anyone got an idea, what I did wrong causing this?

Thanks a bunch
Robert

NameError:
       undefined local variable or method `t' for #<Dry::Schema::Messages::Namespaced config=#<Dry::Configurable::Config values={:default_locale=>:de, :load_paths=>#<Set: {#<Pathname:/usr/local/bundle/gems/dry-schema-1.8.0/config/errors.yml>, #<Pathname:/usr/local/bundle/gems/dry-validation-1.7.0/config/errors.yml>}>, :top_namespace=>"dry_validation", :root=>"dry_validation.errors", :lookup_options=>[:root, :predicate, :path, :val_type, :arg_type], :lookup_paths=>["%<root>s.rules.%<path>s.%<predicate>s.arg.%<arg_type>s", "%<root>s.rules.%<path>s.%<predicate>s", "%<root>s.%<predicate>s.%<message_type>s", "%<root>s.%<predicate>s.value.%<path>s", "%<root>s.%<predicate>s.value.%<val_type>s.arg.%<arg_type>s", "%<root>s.%<predicate>s.value.%<val_type>s", "%<root>s.%<predicate>s.arg.%<arg_type>s", "%<root>s.%<predicate>s"], :rule_lookup_paths=>["dry_validation.rules.%<name>s"], :arg_types=>{Range=>"range"}, :val_types=>{Range=>"range", String=>"string"}}>>
     # /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/messages/abstract.rb:72:in `translate'
     # /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message_compiler.rb:126:in `block in or_translator'
     # /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message/or/single_path.rb:41:in `dump'
     # /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message/or/single_path.rb:53:in `to_h'
     # /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message_set.rb:108:in `map'
     # /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message_set.rb:108:in `messages_map'
     # /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message_set.rb:60:in `to_h'
     # /usr/local/bundle/gems/dry-validation-1.7.0/lib/dry/validation/message_set.rb:92:in `freeze'
     # /usr/local/bundle/gems/dry-validation-1.7.0/lib/dry/validation/result.rb:192:in `freeze'
     # /usr/local/bundle/gems/dry-validation-1.7.0/lib/dry/validation/result.rb:26:in `new'
     # /usr/local/bundle/gems/dry-validation-1.7.0/lib/dry/validation/contract.rb:105:in `call'
     # ./spec/lib/users/contracts/create_spec.rb:40:in `block (3 levels) in <top (required)>'
     # /usr/local/bundle/gems/webmock-3.12.2/lib/webmock/rspec.rb:37:in `block (2 levels) in <top (required)>'
require "pp"
require "i18n"

I18n::Backend::Simple.include(I18n::Backend::Memoize)
I18n::Backend::Simple.include(I18n::Backend::InterpolationCompiler)
I18n::Backend::Simple.include(I18n::Backend::Pluralization)

data = YAML.safe_load_file("/usr/local/bundle/gems/dry-validation-1.7.0/config/errors.yml", symbolize_names: true)
data[:en][:dry_validation][:errors][:Users] = {
  rules: {
    email: { filled?: "How would we send a mail, eh?!" }
  }
}
I18n.backend.store_translations(:de, data[:en])
I18n.default_locale = :de

require "dry-validation"

class BaseContract < Dry::Validation::Contract
  config.messages.default_locale = :de
  config.messages.backend = :i18n
  config.messages.namespace = :Users
end

BaseSchema = Dry::Schema.Params do
  required(:name).value(:string)
end

class MyContract < BaseContract
  params(BaseSchema) do
    required(:email).filled(:string)
    required(:age).value(:integer)
  end

  rule(:email) { key.failure(:filled?) if value&.match?(/@example\.com/) }
end

pp MyContract.new.call(name: "Bob", email: "bob@example.com", age: 42).errors.to_h

Finally tracked it down to a composed Type. Here’s a sample that crashes if the type (Types::String.constrained(format: /\A[a-z]{2}\z/i) | Types::Symbol.constrained(format: /\A[a-z]{2}\z/i)) errors. But only if namespaced messages are used.

require "pp"
require "i18n"
require "yaml"
require "dry-types"
require "dry-validation"

dry_schma_i18n = YAML.safe_load_file("/usr/local/bundle/gems/dry-schema-1.8.0/config/errors.yml", symbolize_names: true)
data = {
  dry_validation: dry_schma_i18n[:en][:dry_schema]
}
data[:dry_validation][:errors][:Users] = {
  rules: {
    langs: { filled?: "need to know a language" }
  }
}
I18n.backend.store_translations(:de, data)
I18n.default_locale = :de

module Types
  include Dry.Types()

  LangT = Types::String.constrained(format: /\A[a-z]{2}\z/i) | Types::Symbol.constrained(format: /\A[a-z]{2}\z/i)
end

class MyContract < Dry::Validation::Contract
  config.messages.default_locale = :de
  config.messages.backend = :i18n
  config.messages.namespace = :Users

  params do
    required(:name).filled(:string)
    optional(:langs).value(Types::Array.of(Types::LangT))
  end
end

pp MyContract.new.call(name: "", langs: ["de"]).errors.to_h
# => {:name=>["must be filled"]}

pp MyContract.new.call(name: "", langs: [""]).errors.to_h
# /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/messages/abstract.rb:72:in `translate': undefined local variable or method `t' for #<Dry::Schema::Messages::Namespaced config=#<Dry::Configurable::Config values={:default_locale=>:de, :load_paths=>#<Set: {#<Pathname:/usr/local/bundle/gems/dry-schema-1.8.0/config/errors.yml>, #<Pathname:/usr/local/bundle/gems/dry-validation-1.7.0/config/errors.yml>}>, :top_namespace=>"dry_validation", :root=>"dry_validation.errors", :lookup_options=>[:root, :predicate, :path, :val_type, :arg_type], :lookup_paths=>["%<root>s.rules.%<path>s.%<predicate>s.arg.%<arg_type>s", "%<root>s.rules.%<path>s.%<predicate>s", "%<root>s.%<predicate>s.%<message_type>s", "%<root>s.%<predicate>s.value.%<path>s", "%<root>s.%<predicate>s.value.%<val_type>s.arg.%<arg_type>s", "%<root>s.%<predicate>s.value.%<val_type>s", "%<root>s.%<predicate>s.arg.%<arg_type>s", "%<root>s.%<predicate>s"], :rule_lookup_paths=>["dry_validation.rules.%<name>s"], :arg_types=>{Range=>"range"}, :val_types=>{Range=>"range", String=>"string"}}>> (NameError)
#   from /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message_compiler.rb:126:in `block in or_translator'
#   from /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message/or/single_path.rb:41:in `dump'
#   from /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message/or/single_path.rb:53:in `to_h'
#   from /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message_set.rb:108:in `map'
#   from /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message_set.rb:108:in `messages_map'
#   from /usr/local/bundle/gems/dry-schema-1.8.0/lib/dry/schema/message_set.rb:60:in `to_h'
#   from /usr/local/bundle/gems/dry-validation-1.7.0/lib/dry/validation/message_set.rb:92:in `freeze'
#   from /usr/local/bundle/gems/dry-validation-1.7.0/lib/dry/validation/result.rb:192:in `freeze'
#   from /usr/local/bundle/gems/dry-validation-1.7.0/lib/dry/validation/result.rb:26:in `new'
#   from /usr/local/bundle/gems/dry-validation-1.7.0/lib/dry/validation/contract.rb:105:in `call'
#   from issue.rb:39:in `<main>'