dry-types 0.15.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.gitignore +1 -0
 - data/.rubocop.yml +18 -2
 - data/.travis.yml +4 -5
 - data/.yardopts +6 -2
 - data/CHANGELOG.md +69 -1
 - data/Gemfile +3 -0
 - data/README.md +2 -1
 - data/Rakefile +2 -0
 - data/benchmarks/hash_schemas.rb +2 -0
 - data/benchmarks/lax_schema.rb +16 -0
 - data/benchmarks/profile_invalid_input.rb +15 -0
 - data/benchmarks/profile_lax_schema_valid.rb +16 -0
 - data/benchmarks/profile_valid_input.rb +15 -0
 - data/benchmarks/schema_valid_vs_invalid.rb +21 -0
 - data/benchmarks/setup.rb +17 -0
 - data/dry-types.gemspec +4 -2
 - data/lib/dry-types.rb +2 -0
 - data/lib/dry/types.rb +51 -13
 - data/lib/dry/types/any.rb +21 -10
 - data/lib/dry/types/array.rb +11 -1
 - data/lib/dry/types/array/member.rb +65 -13
 - data/lib/dry/types/builder.rb +48 -4
 - data/lib/dry/types/builder_methods.rb +9 -8
 - data/lib/dry/types/coercions.rb +71 -19
 - data/lib/dry/types/coercions/json.rb +22 -3
 - data/lib/dry/types/coercions/params.rb +98 -30
 - data/lib/dry/types/compiler.rb +35 -12
 - data/lib/dry/types/constrained.rb +73 -27
 - data/lib/dry/types/constrained/coercible.rb +36 -6
 - data/lib/dry/types/constraints.rb +15 -1
 - data/lib/dry/types/constructor.rb +90 -43
 - data/lib/dry/types/constructor/function.rb +201 -0
 - data/lib/dry/types/container.rb +5 -0
 - data/lib/dry/types/core.rb +7 -5
 - data/lib/dry/types/decorator.rb +36 -9
 - data/lib/dry/types/default.rb +48 -16
 - data/lib/dry/types/enum.rb +30 -16
 - data/lib/dry/types/errors.rb +73 -7
 - data/lib/dry/types/extensions.rb +2 -0
 - data/lib/dry/types/extensions/maybe.rb +43 -4
 - data/lib/dry/types/fn_container.rb +5 -0
 - data/lib/dry/types/hash.rb +22 -3
 - data/lib/dry/types/hash/constructor.rb +13 -0
 - data/lib/dry/types/inflector.rb +2 -0
 - data/lib/dry/types/json.rb +4 -6
 - data/lib/dry/types/{safe.rb → lax.rb} +34 -17
 - data/lib/dry/types/map.rb +63 -29
 - data/lib/dry/types/meta.rb +51 -0
 - data/lib/dry/types/module.rb +7 -2
 - data/lib/dry/types/nominal.rb +105 -13
 - data/lib/dry/types/options.rb +12 -25
 - data/lib/dry/types/params.rb +5 -3
 - data/lib/dry/types/printable.rb +5 -1
 - data/lib/dry/types/printer.rb +58 -57
 - data/lib/dry/types/result.rb +26 -0
 - data/lib/dry/types/schema.rb +169 -66
 - data/lib/dry/types/schema/key.rb +34 -39
 - data/lib/dry/types/spec/types.rb +41 -1
 - data/lib/dry/types/sum.rb +70 -21
 - data/lib/dry/types/type.rb +49 -0
 - data/lib/dry/types/version.rb +3 -1
 - metadata +14 -12
 
    
        data/lib/dry/types/extensions.rb
    CHANGED
    
    
| 
         @@ -1,31 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'dry/monads/maybe'
         
     | 
| 
       2 
4 
     | 
    
         
             
            require 'dry/types/decorator'
         
     | 
| 
       3 
5 
     | 
    
         | 
| 
       4 
6 
     | 
    
         
             
            module Dry
         
     | 
| 
       5 
7 
     | 
    
         
             
              module Types
         
     | 
| 
      
 8 
     | 
    
         
            +
                # Maybe extension provides Maybe types where values are wrapped using `Either` monad
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @api public
         
     | 
| 
       6 
11 
     | 
    
         
             
                class Maybe
         
     | 
| 
       7 
12 
     | 
    
         
             
                  include Type
         
     | 
| 
       8 
13 
     | 
    
         
             
                  include Dry::Equalizer(:type, :options, inspect: false)
         
     | 
| 
       9 
14 
     | 
    
         
             
                  include Decorator
         
     | 
| 
       10 
15 
     | 
    
         
             
                  include Builder
         
     | 
| 
      
 16 
     | 
    
         
            +
                  include Printable
         
     | 
| 
       11 
17 
     | 
    
         
             
                  include Dry::Monads::Maybe::Mixin
         
     | 
| 
       12 
18 
     | 
    
         | 
| 
       13 
19 
     | 
    
         
             
                  # @param [Dry::Monads::Maybe, Object] input
         
     | 
| 
      
 20 
     | 
    
         
            +
                  #
         
     | 
| 
      
 21 
     | 
    
         
            +
                  # @return [Dry::Monads::Maybe]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  #
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def call_unsafe(input = Undefined)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    case input
         
     | 
| 
      
 26 
     | 
    
         
            +
                    when Dry::Monads::Maybe
         
     | 
| 
      
 27 
     | 
    
         
            +
                      input
         
     | 
| 
      
 28 
     | 
    
         
            +
                    when Undefined
         
     | 
| 
      
 29 
     | 
    
         
            +
                      None()
         
     | 
| 
      
 30 
     | 
    
         
            +
                    else
         
     | 
| 
      
 31 
     | 
    
         
            +
                      Maybe(type.call_unsafe(input))
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  # @param [Dry::Monads::Maybe, Object] input
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
       14 
37 
     | 
    
         
             
                  # @return [Dry::Monads::Maybe]
         
     | 
| 
       15 
     | 
    
         
            -
                   
     | 
| 
      
 38 
     | 
    
         
            +
                  #
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 40 
     | 
    
         
            +
                  def call_safe(input = Undefined, &block)
         
     | 
| 
       16 
41 
     | 
    
         
             
                    case input
         
     | 
| 
       17 
42 
     | 
    
         
             
                    when Dry::Monads::Maybe
         
     | 
| 
       18 
43 
     | 
    
         
             
                      input
         
     | 
| 
       19 
44 
     | 
    
         
             
                    when Undefined
         
     | 
| 
       20 
45 
     | 
    
         
             
                      None()
         
     | 
| 
       21 
46 
     | 
    
         
             
                    else
         
     | 
| 
       22 
     | 
    
         
            -
                      Maybe(type 
     | 
| 
      
 47 
     | 
    
         
            +
                      Maybe(type.call_safe(input, &block))
         
     | 
| 
       23 
48 
     | 
    
         
             
                    end
         
     | 
| 
       24 
49 
     | 
    
         
             
                  end
         
     | 
| 
       25 
     | 
    
         
            -
                  alias_method :[], :call
         
     | 
| 
       26 
50 
     | 
    
         | 
| 
       27 
51 
     | 
    
         
             
                  # @param [Object] input
         
     | 
| 
      
 52 
     | 
    
         
            +
                  #
         
     | 
| 
       28 
53 
     | 
    
         
             
                  # @return [Result::Success]
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       29 
56 
     | 
    
         
             
                  def try(input = Undefined)
         
     | 
| 
       30 
57 
     | 
    
         
             
                    res = if input.equal?(Undefined)
         
     | 
| 
       31 
58 
     | 
    
         
             
                            None()
         
     | 
| 
         @@ -37,13 +64,19 @@ module Dry 
     | 
|
| 
       37 
64 
     | 
    
         
             
                  end
         
     | 
| 
       38 
65 
     | 
    
         | 
| 
       39 
66 
     | 
    
         
             
                  # @return [true]
         
     | 
| 
      
 67 
     | 
    
         
            +
                  #
         
     | 
| 
      
 68 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       40 
69 
     | 
    
         
             
                  def default?
         
     | 
| 
       41 
70 
     | 
    
         
             
                    true
         
     | 
| 
       42 
71 
     | 
    
         
             
                  end
         
     | 
| 
       43 
72 
     | 
    
         | 
| 
       44 
73 
     | 
    
         
             
                  # @param [Object] value
         
     | 
| 
      
 74 
     | 
    
         
            +
                  #
         
     | 
| 
       45 
75 
     | 
    
         
             
                  # @see Dry::Types::Builder#default
         
     | 
| 
      
 76 
     | 
    
         
            +
                  #
         
     | 
| 
       46 
77 
     | 
    
         
             
                  # @raise [ArgumentError] if nil provided as default value
         
     | 
| 
      
 78 
     | 
    
         
            +
                  #
         
     | 
| 
      
 79 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       47 
80 
     | 
    
         
             
                  def default(value)
         
     | 
| 
       48 
81 
     | 
    
         
             
                    if value.nil?
         
     | 
| 
       49 
82 
     | 
    
         
             
                      raise ArgumentError, "nil cannot be used as a default of a maybe type"
         
     | 
| 
         @@ -54,18 +87,24 @@ module Dry 
     | 
|
| 
       54 
87 
     | 
    
         
             
                end
         
     | 
| 
       55 
88 
     | 
    
         | 
| 
       56 
89 
     | 
    
         
             
                module Builder
         
     | 
| 
      
 90 
     | 
    
         
            +
                  # Turn a type into a maybe type
         
     | 
| 
      
 91 
     | 
    
         
            +
                  #
         
     | 
| 
       57 
92 
     | 
    
         
             
                  # @return [Maybe]
         
     | 
| 
      
 93 
     | 
    
         
            +
                  #
         
     | 
| 
      
 94 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       58 
95 
     | 
    
         
             
                  def maybe
         
     | 
| 
       59 
96 
     | 
    
         
             
                    Maybe.new(Types['strict.nil'] | self)
         
     | 
| 
       60 
97 
     | 
    
         
             
                  end
         
     | 
| 
       61 
98 
     | 
    
         
             
                end
         
     | 
| 
       62 
99 
     | 
    
         | 
| 
      
 100 
     | 
    
         
            +
                # @api private
         
     | 
| 
       63 
101 
     | 
    
         
             
                class Printer
         
     | 
| 
       64 
102 
     | 
    
         
             
                  MAPPING[Maybe] = :visit_maybe
         
     | 
| 
       65 
103 
     | 
    
         | 
| 
      
 104 
     | 
    
         
            +
                  # @api private
         
     | 
| 
       66 
105 
     | 
    
         
             
                  def visit_maybe(maybe)
         
     | 
| 
       67 
106 
     | 
    
         
             
                    visit(maybe.type) do |type|
         
     | 
| 
       68 
     | 
    
         
            -
                      yield "Maybe<#{ 
     | 
| 
      
 107 
     | 
    
         
            +
                      yield "Maybe<#{type}>"
         
     | 
| 
       69 
108 
     | 
    
         
             
                    end
         
     | 
| 
       70 
109 
     | 
    
         
             
                  end
         
     | 
| 
       71 
110 
     | 
    
         
             
                end
         
     | 
    
        data/lib/dry/types/hash.rb
    CHANGED
    
    | 
         @@ -1,18 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'dry/types/hash/constructor'
         
     | 
| 
       2 
4 
     | 
    
         | 
| 
       3 
5 
     | 
    
         
             
            module Dry
         
     | 
| 
       4 
6 
     | 
    
         
             
              module Types
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Hash types can be used to define maps and schemas
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @api public
         
     | 
| 
       5 
10 
     | 
    
         
             
                class Hash < Nominal
         
     | 
| 
       6 
11 
     | 
    
         
             
                  NOT_REQUIRED = { required: false }.freeze
         
     | 
| 
       7 
12 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
                  # @overload  
     | 
| 
      
 13 
     | 
    
         
            +
                  # @overload schema(type_map, meta = EMPTY_HASH)
         
     | 
| 
       9 
14 
     | 
    
         
             
                  #   @param [{Symbol => Dry::Types::Nominal}] type_map
         
     | 
| 
       10 
15 
     | 
    
         
             
                  #   @param [Hash] meta
         
     | 
| 
       11 
16 
     | 
    
         
             
                  #   @return [Dry::Types::Schema]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #
         
     | 
| 
       12 
18 
     | 
    
         
             
                  # @overload schema(keys)
         
     | 
| 
       13 
19 
     | 
    
         
             
                  #   @param [Array<Dry::Types::Schema::Key>] key List of schema keys
         
     | 
| 
       14 
20 
     | 
    
         
             
                  #   @param [Hash] meta
         
     | 
| 
       15 
21 
     | 
    
         
             
                  #   @return [Dry::Types::Schema]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  #
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       16 
24 
     | 
    
         
             
                  def schema(keys_or_map, meta = EMPTY_HASH)
         
     | 
| 
       17 
25 
     | 
    
         
             
                    if keys_or_map.is_a?(::Array)
         
     | 
| 
       18 
26 
     | 
    
         
             
                      keys = keys_or_map
         
     | 
| 
         @@ -27,7 +35,10 @@ module Dry 
     | 
|
| 
       27 
35 
     | 
    
         
             
                  #
         
     | 
| 
       28 
36 
     | 
    
         
             
                  # @param [Type] key_type
         
     | 
| 
       29 
37 
     | 
    
         
             
                  # @param [Type] value_type
         
     | 
| 
      
 38 
     | 
    
         
            +
                  #
         
     | 
| 
       30 
39 
     | 
    
         
             
                  # @return [Map]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  #
         
     | 
| 
      
 41 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       31 
42 
     | 
    
         
             
                  def map(key_type, value_type)
         
     | 
| 
       32 
43 
     | 
    
         
             
                    Map.new(
         
     | 
| 
       33 
44 
     | 
    
         
             
                      primitive,
         
     | 
| 
         @@ -37,8 +48,7 @@ module Dry 
     | 
|
| 
       37 
48 
     | 
    
         
             
                    )
         
     | 
| 
       38 
49 
     | 
    
         
             
                  end
         
     | 
| 
       39 
50 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
                  # @ 
     | 
| 
       41 
     | 
    
         
            -
                  # @return [Schema]
         
     | 
| 
      
 51 
     | 
    
         
            +
                  # @api private
         
     | 
| 
       42 
52 
     | 
    
         
             
                  def weak(*)
         
     | 
| 
       43 
53 
     | 
    
         
             
                    raise "Support for old hash schemas was removed, please refer to the CHANGELOG "\
         
     | 
| 
       44 
54 
     | 
    
         
             
                          "on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md"
         
     | 
| 
         @@ -49,9 +59,13 @@ module Dry 
     | 
|
| 
       49 
59 
     | 
    
         
             
                  alias_method :symbolized, :weak
         
     | 
| 
       50 
60 
     | 
    
         | 
| 
       51 
61 
     | 
    
         
             
                  # Injects a type transformation function for building schemas
         
     | 
| 
      
 62 
     | 
    
         
            +
                  #
         
     | 
| 
       52 
63 
     | 
    
         
             
                  # @param [#call,nil] proc
         
     | 
| 
       53 
64 
     | 
    
         
             
                  # @param [#call,nil] block
         
     | 
| 
      
 65 
     | 
    
         
            +
                  #
         
     | 
| 
       54 
66 
     | 
    
         
             
                  # @return [Hash]
         
     | 
| 
      
 67 
     | 
    
         
            +
                  #
         
     | 
| 
      
 68 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       55 
69 
     | 
    
         
             
                  def with_type_transform(proc = nil, &block)
         
     | 
| 
       56 
70 
     | 
    
         
             
                    fn = proc || block
         
     | 
| 
       57 
71 
     | 
    
         | 
| 
         @@ -69,14 +83,19 @@ module Dry 
     | 
|
| 
       69 
83 
     | 
    
         
             
                  end
         
     | 
| 
       70 
84 
     | 
    
         | 
| 
       71 
85 
     | 
    
         
             
                  # Whether the type transforms types of schemas created by {Dry::Types::Hash#schema}
         
     | 
| 
      
 86 
     | 
    
         
            +
                  #
         
     | 
| 
       72 
87 
     | 
    
         
             
                  # @return [Boolean]
         
     | 
| 
      
 88 
     | 
    
         
            +
                  #
         
     | 
| 
       73 
89 
     | 
    
         
             
                  # @api public
         
     | 
| 
       74 
90 
     | 
    
         
             
                  def transform_types?
         
     | 
| 
       75 
91 
     | 
    
         
             
                    !options[:type_transform_fn].nil?
         
     | 
| 
       76 
92 
     | 
    
         
             
                  end
         
     | 
| 
       77 
93 
     | 
    
         | 
| 
       78 
94 
     | 
    
         
             
                  # @param meta [Boolean] Whether to dump the meta to the AST
         
     | 
| 
      
 95 
     | 
    
         
            +
                  #
         
     | 
| 
       79 
96 
     | 
    
         
             
                  # @return [Array] An AST representation
         
     | 
| 
      
 97 
     | 
    
         
            +
                  #
         
     | 
| 
      
 98 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       80 
99 
     | 
    
         
             
                  def to_ast(meta: true)
         
     | 
| 
       81 
100 
     | 
    
         
             
                    if RUBY_VERSION >= "2.5"
         
     | 
| 
       82 
101 
     | 
    
         
             
                      opts = options.slice(:type_transform_fn)
         
     | 
| 
         @@ -1,7 +1,12 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'dry/types/constructor'
         
     | 
| 
       2 
4 
     | 
    
         | 
| 
       3 
5 
     | 
    
         
             
            module Dry
         
     | 
| 
       4 
6 
     | 
    
         
             
              module Types
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Hash type exposes additional APIs for working with schema hashes
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @api public
         
     | 
| 
       5 
10 
     | 
    
         
             
                class Hash < Nominal
         
     | 
| 
       6 
11 
     | 
    
         
             
                  class Constructor < ::Dry::Types::Constructor
         
     | 
| 
       7 
12 
     | 
    
         
             
                    # @api private
         
     | 
| 
         @@ -9,8 +14,16 @@ module Dry 
     | 
|
| 
       9 
14 
     | 
    
         
             
                      ::Dry::Types::Hash::Constructor
         
     | 
| 
       10 
15 
     | 
    
         
             
                    end
         
     | 
| 
       11 
16 
     | 
    
         | 
| 
      
 17 
     | 
    
         
            +
                    # @return [Lax]
         
     | 
| 
      
 18 
     | 
    
         
            +
                    #
         
     | 
| 
      
 19 
     | 
    
         
            +
                    # @api public
         
     | 
| 
      
 20 
     | 
    
         
            +
                    def lax
         
     | 
| 
      
 21 
     | 
    
         
            +
                      type.lax.constructor(fn, meta: meta)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
       12 
24 
     | 
    
         
             
                    private
         
     | 
| 
       13 
25 
     | 
    
         | 
| 
      
 26 
     | 
    
         
            +
                    # @api private
         
     | 
| 
       14 
27 
     | 
    
         
             
                    def composable?(value)
         
     | 
| 
       15 
28 
     | 
    
         
             
                      super && !value.is_a?(Schema::Key)
         
     | 
| 
       16 
29 
     | 
    
         
             
                    end
         
     | 
    
        data/lib/dry/types/inflector.rb
    CHANGED
    
    
    
        data/lib/dry/types/json.rb
    CHANGED
    
    | 
         @@ -1,3 +1,5 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'dry/types/coercions/json'
         
     | 
| 
       2 
4 
     | 
    
         | 
| 
       3 
5 
     | 
    
         
             
            module Dry
         
     | 
| 
         @@ -22,12 +24,8 @@ module Dry 
     | 
|
| 
       22 
24 
     | 
    
         
             
                  self['nominal.decimal'].constructor(Coercions::JSON.method(:to_decimal))
         
     | 
| 
       23 
25 
     | 
    
         
             
                end
         
     | 
| 
       24 
26 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
                register('json.array')  
     | 
| 
       26 
     | 
    
         
            -
                  self['nominal.array'].safe
         
     | 
| 
       27 
     | 
    
         
            -
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
                register('json.array') { self['array'] }
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                register('json.hash')  
     | 
| 
       30 
     | 
    
         
            -
                  self['nominal.hash'].safe
         
     | 
| 
       31 
     | 
    
         
            -
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
                register('json.hash') { self['hash'] }
         
     | 
| 
       32 
30 
     | 
    
         
             
              end
         
     | 
| 
       33 
31 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,61 +1,78 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'dry/core/deprecations'
         
     | 
| 
       1 
4 
     | 
    
         
             
            require 'dry/types/decorator'
         
     | 
| 
       2 
5 
     | 
    
         | 
| 
       3 
6 
     | 
    
         
             
            module Dry
         
     | 
| 
       4 
7 
     | 
    
         
             
              module Types
         
     | 
| 
       5 
     | 
    
         
            -
                 
     | 
| 
      
 8 
     | 
    
         
            +
                # Lax types rescue from type-related errors when constructors fail
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 11 
     | 
    
         
            +
                class Lax
         
     | 
| 
       6 
12 
     | 
    
         
             
                  include Type
         
     | 
| 
       7 
13 
     | 
    
         
             
                  include Decorator
         
     | 
| 
       8 
14 
     | 
    
         
             
                  include Builder
         
     | 
| 
       9 
15 
     | 
    
         
             
                  include Printable
         
     | 
| 
       10 
16 
     | 
    
         
             
                  include Dry::Equalizer(:type, inspect: false)
         
     | 
| 
       11 
17 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
                  private :options, : 
     | 
| 
      
 18 
     | 
    
         
            +
                  private :options, :constructor
         
     | 
| 
       13 
19 
     | 
    
         | 
| 
       14 
20 
     | 
    
         
             
                  # @param [Object] input
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #
         
     | 
| 
       15 
22 
     | 
    
         
             
                  # @return [Object]
         
     | 
| 
      
 23 
     | 
    
         
            +
                  #
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       16 
25 
     | 
    
         
             
                  def call(input)
         
     | 
| 
       17 
     | 
    
         
            -
                     
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                    if result.respond_to?(:input)
         
     | 
| 
       20 
     | 
    
         
            -
                      result.input
         
     | 
| 
       21 
     | 
    
         
            -
                    else
         
     | 
| 
       22 
     | 
    
         
            -
                      input
         
     | 
| 
       23 
     | 
    
         
            -
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
                    type.call_safe(input) { |output = input| output }
         
     | 
| 
       24 
27 
     | 
    
         
             
                  end
         
     | 
| 
       25 
28 
     | 
    
         
             
                  alias_method :[], :call
         
     | 
| 
      
 29 
     | 
    
         
            +
                  alias_method :call_safe, :call
         
     | 
| 
      
 30 
     | 
    
         
            +
                  alias_method :call_unsafe, :call
         
     | 
| 
       26 
31 
     | 
    
         | 
| 
       27 
32 
     | 
    
         
             
                  # @param [Object] input
         
     | 
| 
       28 
33 
     | 
    
         
             
                  # @param [#call,nil] block
         
     | 
| 
      
 34 
     | 
    
         
            +
                  #
         
     | 
| 
       29 
35 
     | 
    
         
             
                  # @yieldparam [Failure] failure
         
     | 
| 
       30 
36 
     | 
    
         
             
                  # @yieldreturn [Result]
         
     | 
| 
      
 37 
     | 
    
         
            +
                  #
         
     | 
| 
       31 
38 
     | 
    
         
             
                  # @return [Result,Logic::Result]
         
     | 
| 
      
 39 
     | 
    
         
            +
                  #
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       32 
41 
     | 
    
         
             
                  def try(input, &block)
         
     | 
| 
       33 
42 
     | 
    
         
             
                    type.try(input, &block)
         
     | 
| 
       34 
     | 
    
         
            -
                  rescue  
     | 
| 
       35 
     | 
    
         
            -
                    result = failure(input,  
     | 
| 
      
 43 
     | 
    
         
            +
                  rescue CoercionError => error
         
     | 
| 
      
 44 
     | 
    
         
            +
                    result = failure(input, error.message)
         
     | 
| 
       36 
45 
     | 
    
         
             
                    block ? yield(result) : result
         
     | 
| 
       37 
46 
     | 
    
         
             
                  end
         
     | 
| 
       38 
47 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
                  # @api public
         
     | 
| 
       40 
     | 
    
         
            -
                  #
         
     | 
| 
       41 
48 
     | 
    
         
             
                  # @see Nominal#to_ast
         
     | 
| 
      
 49 
     | 
    
         
            +
                  #
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       42 
51 
     | 
    
         
             
                  def to_ast(meta: true)
         
     | 
| 
       43 
     | 
    
         
            -
                    [: 
     | 
| 
      
 52 
     | 
    
         
            +
                    [:lax, type.to_ast(meta: meta)]
         
     | 
| 
       44 
53 
     | 
    
         
             
                  end
         
     | 
| 
       45 
54 
     | 
    
         | 
| 
      
 55 
     | 
    
         
            +
                  # @return [Lax]
         
     | 
| 
      
 56 
     | 
    
         
            +
                  #
         
     | 
| 
       46 
57 
     | 
    
         
             
                  # @api public
         
     | 
| 
       47 
     | 
    
         
            -
                   
     | 
| 
       48 
     | 
    
         
            -
                  def safe
         
     | 
| 
      
 58 
     | 
    
         
            +
                  def lax
         
     | 
| 
       49 
59 
     | 
    
         
             
                    self
         
     | 
| 
       50 
60 
     | 
    
         
             
                  end
         
     | 
| 
       51 
61 
     | 
    
         | 
| 
       52 
62 
     | 
    
         
             
                  private
         
     | 
| 
       53 
63 
     | 
    
         | 
| 
       54 
64 
     | 
    
         
             
                  # @param [Object, Dry::Types::Constructor] response
         
     | 
| 
      
 65 
     | 
    
         
            +
                  #
         
     | 
| 
       55 
66 
     | 
    
         
             
                  # @return [Boolean]
         
     | 
| 
      
 67 
     | 
    
         
            +
                  #
         
     | 
| 
      
 68 
     | 
    
         
            +
                  # @api private
         
     | 
| 
       56 
69 
     | 
    
         
             
                  def decorate?(response)
         
     | 
| 
       57 
     | 
    
         
            -
                    super || response. 
     | 
| 
      
 70 
     | 
    
         
            +
                    super || response.is_a?(constructor_type)
         
     | 
| 
       58 
71 
     | 
    
         
             
                  end
         
     | 
| 
       59 
72 
     | 
    
         
             
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                extend ::Dry::Core::Deprecations[:'dry-types']
         
     | 
| 
      
 75 
     | 
    
         
            +
                Safe = Lax
         
     | 
| 
      
 76 
     | 
    
         
            +
                deprecate_constant(:Safe)
         
     | 
| 
       60 
77 
     | 
    
         
             
              end
         
     | 
| 
       61 
78 
     | 
    
         
             
            end
         
     | 
    
        data/lib/dry/types/map.rb
    CHANGED
    
    | 
         @@ -1,44 +1,76 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module Dry
         
     | 
| 
       2 
4 
     | 
    
         
             
              module Types
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Homogeneous mapping. It describes a hash with unknown keys that match a certain type.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 8 
     | 
    
         
            +
                #   type = Dry::Types['hash'].map(
         
     | 
| 
      
 9 
     | 
    
         
            +
                #     Dry::Types['integer'].constrained(gteq: 1, lteq: 10),
         
     | 
| 
      
 10 
     | 
    
         
            +
                #     Dry::Types['string']
         
     | 
| 
      
 11 
     | 
    
         
            +
                #   )
         
     | 
| 
      
 12 
     | 
    
         
            +
                #
         
     | 
| 
      
 13 
     | 
    
         
            +
                #   type.(1 => 'right')
         
     | 
| 
      
 14 
     | 
    
         
            +
                #   # => {1 => 'right'}
         
     | 
| 
      
 15 
     | 
    
         
            +
                #
         
     | 
| 
      
 16 
     | 
    
         
            +
                #   type.('1' => 'wrong')
         
     | 
| 
      
 17 
     | 
    
         
            +
                #   # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1") AND gteq?(1, "1") AND lteq?(10, "1") failed)
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                #   type.(11 => 'wrong')
         
     | 
| 
      
 20 
     | 
    
         
            +
                #   # Dry::Types::MapError: 11 violates constraints (lteq?(10, 11) failed)
         
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
      
 22 
     | 
    
         
            +
                # @api public
         
     | 
| 
       3 
23 
     | 
    
         
             
                class Map < Nominal
         
     | 
| 
       4 
24 
     | 
    
         
             
                  def initialize(_primitive, key_type: Types['any'], value_type: Types['any'], meta: EMPTY_HASH)
         
     | 
| 
       5 
25 
     | 
    
         
             
                    super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
         
     | 
| 
       6 
     | 
    
         
            -
                    validate_options!
         
     | 
| 
       7 
26 
     | 
    
         
             
                  end
         
     | 
| 
       8 
27 
     | 
    
         | 
| 
       9 
28 
     | 
    
         
             
                  # @return [Type]
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       10 
31 
     | 
    
         
             
                  def key_type
         
     | 
| 
       11 
32 
     | 
    
         
             
                    options[:key_type]
         
     | 
| 
       12 
33 
     | 
    
         
             
                  end
         
     | 
| 
       13 
34 
     | 
    
         | 
| 
       14 
35 
     | 
    
         
             
                  # @return [Type]
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       15 
38 
     | 
    
         
             
                  def value_type
         
     | 
| 
       16 
39 
     | 
    
         
             
                    options[:value_type]
         
     | 
| 
       17 
40 
     | 
    
         
             
                  end
         
     | 
| 
       18 
41 
     | 
    
         | 
| 
       19 
42 
     | 
    
         
             
                  # @return [String]
         
     | 
| 
      
 43 
     | 
    
         
            +
                  #
         
     | 
| 
      
 44 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       20 
45 
     | 
    
         
             
                  def name
         
     | 
| 
       21 
     | 
    
         
            -
                     
     | 
| 
      
 46 
     | 
    
         
            +
                    'Map'
         
     | 
| 
       22 
47 
     | 
    
         
             
                  end
         
     | 
| 
       23 
48 
     | 
    
         | 
| 
       24 
49 
     | 
    
         
             
                  # @param [Hash] hash
         
     | 
| 
      
 50 
     | 
    
         
            +
                  #
         
     | 
| 
       25 
51 
     | 
    
         
             
                  # @return [Hash]
         
     | 
| 
       26 
     | 
    
         
            -
                   
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
                     
     | 
| 
      
 52 
     | 
    
         
            +
                  #
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 54 
     | 
    
         
            +
                  def call_unsafe(hash)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    try(hash) { |failure|
         
     | 
| 
      
 56 
     | 
    
         
            +
                      raise MapError, failure.error.message
         
     | 
| 
      
 57 
     | 
    
         
            +
                    }.input
         
     | 
| 
       30 
58 
     | 
    
         
             
                  end
         
     | 
| 
       31 
     | 
    
         
            -
                  alias_method :[], :call
         
     | 
| 
       32 
59 
     | 
    
         | 
| 
       33 
60 
     | 
    
         
             
                  # @param [Hash] hash
         
     | 
| 
       34 
     | 
    
         
            -
                  # 
     | 
| 
       35 
     | 
    
         
            -
                   
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
      
 61 
     | 
    
         
            +
                  #
         
     | 
| 
      
 62 
     | 
    
         
            +
                  # @return [Hash]
         
     | 
| 
      
 63 
     | 
    
         
            +
                  #
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 65 
     | 
    
         
            +
                  def call_safe(hash)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    try(hash) { return yield }.input
         
     | 
| 
       37 
67 
     | 
    
         
             
                  end
         
     | 
| 
       38 
     | 
    
         
            -
                  alias_method :===, :valid?
         
     | 
| 
       39 
68 
     | 
    
         | 
| 
       40 
69 
     | 
    
         
             
                  # @param [Hash] hash
         
     | 
| 
      
 70 
     | 
    
         
            +
                  #
         
     | 
| 
       41 
71 
     | 
    
         
             
                  # @return [Result]
         
     | 
| 
      
 72 
     | 
    
         
            +
                  #
         
     | 
| 
      
 73 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       42 
74 
     | 
    
         
             
                  def try(hash)
         
     | 
| 
       43 
75 
     | 
    
         
             
                    result = coerce(hash)
         
     | 
| 
       44 
76 
     | 
    
         
             
                    return result if result.success? || !block_given?
         
     | 
| 
         @@ -46,51 +78,53 @@ module Dry 
     | 
|
| 
       46 
78 
     | 
    
         
             
                  end
         
     | 
| 
       47 
79 
     | 
    
         | 
| 
       48 
80 
     | 
    
         
             
                  # @param meta [Boolean] Whether to dump the meta to the AST
         
     | 
| 
      
 81 
     | 
    
         
            +
                  #
         
     | 
| 
       49 
82 
     | 
    
         
             
                  # @return [Array] An AST representation
         
     | 
| 
      
 83 
     | 
    
         
            +
                  #
         
     | 
| 
      
 84 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       50 
85 
     | 
    
         
             
                  def to_ast(meta: true)
         
     | 
| 
       51 
86 
     | 
    
         
             
                    [:map,
         
     | 
| 
       52 
     | 
    
         
            -
                     [key_type.to_ast(meta: true), 
     | 
| 
      
 87 
     | 
    
         
            +
                     [key_type.to_ast(meta: true),
         
     | 
| 
      
 88 
     | 
    
         
            +
                      value_type.to_ast(meta: true),
         
     | 
| 
       53 
89 
     | 
    
         
             
                      meta ? self.meta : EMPTY_HASH]]
         
     | 
| 
       54 
90 
     | 
    
         
             
                  end
         
     | 
| 
       55 
91 
     | 
    
         | 
| 
       56 
92 
     | 
    
         
             
                  # @return [Boolean]
         
     | 
| 
      
 93 
     | 
    
         
            +
                  #
         
     | 
| 
      
 94 
     | 
    
         
            +
                  # @api public
         
     | 
| 
       57 
95 
     | 
    
         
             
                  def constrained?
         
     | 
| 
       58 
96 
     | 
    
         
             
                    value_type.constrained?
         
     | 
| 
       59 
97 
     | 
    
         
             
                  end
         
     | 
| 
       60 
98 
     | 
    
         | 
| 
       61 
99 
     | 
    
         
             
                  private
         
     | 
| 
       62 
100 
     | 
    
         | 
| 
      
 101 
     | 
    
         
            +
                  # @api private
         
     | 
| 
       63 
102 
     | 
    
         
             
                  def coerce(input)
         
     | 
| 
       64 
103 
     | 
    
         
             
                    return failure(
         
     | 
| 
       65 
     | 
    
         
            -
                      input, "#{input.inspect} must be an instance of #{primitive}"
         
     | 
| 
      
 104 
     | 
    
         
            +
                      input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}")
         
     | 
| 
       66 
105 
     | 
    
         
             
                    ) unless primitive?(input)
         
     | 
| 
       67 
106 
     | 
    
         | 
| 
       68 
107 
     | 
    
         
             
                    output, failures = {}, []
         
     | 
| 
       69 
108 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
                    input.each do |k,v|
         
     | 
| 
       71 
     | 
    
         
            -
                      res_k =  
     | 
| 
       72 
     | 
    
         
            -
                      res_v =  
     | 
| 
      
 109 
     | 
    
         
            +
                    input.each do |k, v|
         
     | 
| 
      
 110 
     | 
    
         
            +
                      res_k = key_type.try(k)
         
     | 
| 
      
 111 
     | 
    
         
            +
                      res_v = value_type.try(v)
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
       73 
113 
     | 
    
         
             
                      if res_k.failure?
         
     | 
| 
       74 
     | 
    
         
            -
                        failures <<  
     | 
| 
      
 114 
     | 
    
         
            +
                        failures << res_k.error
         
     | 
| 
       75 
115 
     | 
    
         
             
                      elsif output.key?(res_k.input)
         
     | 
| 
       76 
     | 
    
         
            -
                        failures << "duplicate coerced hash key #{res_k.input.inspect}"
         
     | 
| 
      
 116 
     | 
    
         
            +
                        failures << CoercionError.new("duplicate coerced hash key #{res_k.input.inspect}")
         
     | 
| 
       77 
117 
     | 
    
         
             
                      elsif res_v.failure?
         
     | 
| 
       78 
     | 
    
         
            -
                        failures <<  
     | 
| 
      
 118 
     | 
    
         
            +
                        failures << res_v.error
         
     | 
| 
       79 
119 
     | 
    
         
             
                      else
         
     | 
| 
       80 
120 
     | 
    
         
             
                        output[res_k.input] = res_v.input
         
     | 
| 
       81 
121 
     | 
    
         
             
                      end
         
     | 
| 
       82 
122 
     | 
    
         
             
                    end
         
     | 
| 
       83 
123 
     | 
    
         | 
| 
       84 
     | 
    
         
            -
                     
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
                     
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
     | 
    
         
            -
                  def validate_options!
         
     | 
| 
       90 
     | 
    
         
            -
                    %i(key_type value_type).each do |opt|
         
     | 
| 
       91 
     | 
    
         
            -
                      type = send(opt)
         
     | 
| 
       92 
     | 
    
         
            -
                      next if type.is_a?(Type)
         
     | 
| 
       93 
     | 
    
         
            -
                      raise MapError, ":#{opt} must be a #{Type}, got: #{type.inspect}"
         
     | 
| 
      
 124 
     | 
    
         
            +
                    if failures.empty?
         
     | 
| 
      
 125 
     | 
    
         
            +
                      success(output)
         
     | 
| 
      
 126 
     | 
    
         
            +
                    else
         
     | 
| 
      
 127 
     | 
    
         
            +
                      failure(input, MultipleError.new(failures))
         
     | 
| 
       94 
128 
     | 
    
         
             
                    end
         
     | 
| 
       95 
129 
     | 
    
         
             
                  end
         
     | 
| 
       96 
130 
     | 
    
         
             
                end
         
     |