dry-types 0.15.0 → 1.5.1

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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +547 -161
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +27 -30
  6. data/lib/dry/types/any.rb +23 -12
  7. data/lib/dry/types/array/constructor.rb +32 -0
  8. data/lib/dry/types/array/member.rb +74 -15
  9. data/lib/dry/types/array.rb +18 -2
  10. data/lib/dry/types/builder.rb +118 -22
  11. data/lib/dry/types/builder_methods.rb +46 -16
  12. data/lib/dry/types/coercions/json.rb +43 -7
  13. data/lib/dry/types/coercions/params.rb +117 -32
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/compiler.rb +44 -21
  16. data/lib/dry/types/constrained/coercible.rb +36 -6
  17. data/lib/dry/types/constrained.rb +79 -31
  18. data/lib/dry/types/constraints.rb +18 -4
  19. data/lib/dry/types/constructor/function.rb +216 -0
  20. data/lib/dry/types/constructor/wrapper.rb +94 -0
  21. data/lib/dry/types/constructor.rb +110 -61
  22. data/lib/dry/types/container.rb +6 -1
  23. data/lib/dry/types/core.rb +34 -11
  24. data/lib/dry/types/decorator.rb +38 -17
  25. data/lib/dry/types/default.rb +61 -16
  26. data/lib/dry/types/enum.rb +36 -20
  27. data/lib/dry/types/errors.rb +74 -8
  28. data/lib/dry/types/extensions/maybe.rb +65 -17
  29. data/lib/dry/types/extensions/monads.rb +29 -0
  30. data/lib/dry/types/extensions.rb +7 -1
  31. data/lib/dry/types/fn_container.rb +6 -1
  32. data/lib/dry/types/hash/constructor.rb +17 -4
  33. data/lib/dry/types/hash.rb +32 -20
  34. data/lib/dry/types/inflector.rb +3 -1
  35. data/lib/dry/types/json.rb +18 -16
  36. data/lib/dry/types/lax.rb +75 -0
  37. data/lib/dry/types/map.rb +70 -32
  38. data/lib/dry/types/meta.rb +51 -0
  39. data/lib/dry/types/module.rb +16 -11
  40. data/lib/dry/types/nominal.rb +113 -22
  41. data/lib/dry/types/options.rb +12 -25
  42. data/lib/dry/types/params.rb +39 -25
  43. data/lib/dry/types/predicate_inferrer.rb +238 -0
  44. data/lib/dry/types/predicate_registry.rb +34 -0
  45. data/lib/dry/types/primitive_inferrer.rb +97 -0
  46. data/lib/dry/types/printable.rb +5 -1
  47. data/lib/dry/types/printer.rb +63 -57
  48. data/lib/dry/types/result.rb +29 -3
  49. data/lib/dry/types/schema/key.rb +62 -36
  50. data/lib/dry/types/schema.rb +201 -91
  51. data/lib/dry/types/spec/types.rb +99 -37
  52. data/lib/dry/types/sum.rb +75 -25
  53. data/lib/dry/types/type.rb +49 -0
  54. data/lib/dry/types/version.rb +3 -1
  55. data/lib/dry/types.rb +106 -48
  56. data/lib/dry-types.rb +3 -1
  57. metadata +55 -78
  58. data/.codeclimate.yml +0 -15
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.rubocop.yml +0 -43
  62. data/.travis.yml +0 -28
  63. data/.yardopts +0 -5
  64. data/CONTRIBUTING.md +0 -29
  65. data/Gemfile +0 -23
  66. data/Rakefile +0 -20
  67. data/benchmarks/hash_schemas.rb +0 -51
  68. data/lib/dry/types/safe.rb +0 -61
  69. data/log/.gitkeep +0 -0
@@ -1,9 +1,18 @@
1
- require 'dry/types/fn_container'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/types/fn_container"
5
+ require "dry/types/constructor/function"
6
+ require "dry/types/constructor/wrapper"
2
7
 
3
8
  module Dry
4
9
  module Types
10
+ # Constructor types apply a function to the input that is supposed to return
11
+ # a new value. Coercion is a common use case for constructor types.
12
+ #
13
+ # @api public
5
14
  class Constructor < Nominal
6
- include Dry::Equalizer(:type, :options, :meta, inspect: false)
15
+ include Dry::Equalizer(:type, :options, inspect: false, immutable: true)
7
16
 
8
17
  # @return [#call]
9
18
  attr_reader :fn
@@ -11,135 +20,179 @@ module Dry
11
20
  # @return [Type]
12
21
  attr_reader :type
13
22
 
14
- undef :constrained?
23
+ undef :constrained?, :meta, :optional?, :primitive, :default?, :name
15
24
 
16
25
  # @param [Builder, Object] input
17
26
  # @param [Hash] options
18
27
  # @param [#call, nil] block
28
+ #
29
+ # @api public
19
30
  def self.new(input, **options, &block)
20
31
  type = input.is_a?(Builder) ? input : Nominal.new(input)
21
- super(type, **options, &block)
32
+ super(type, **options, fn: Function[options.fetch(:fn, block)])
22
33
  end
23
34
 
24
- # @param [Type] type
35
+ # @param [Builder, Object] input
25
36
  # @param [Hash] options
26
37
  # @param [#call, nil] block
27
- def initialize(type, **options, &block)
28
- @type = type
29
- @fn = options.fetch(:fn, block)
30
-
31
- raise ArgumentError, 'Missing constructor block' if fn.nil?
38
+ #
39
+ # @api public
40
+ def self.[](type, fn:, **options)
41
+ function = Function[fn]
32
42
 
33
- super(type, **options, fn: fn)
43
+ if function.wrapper?
44
+ wrapper_type.new(type, fn: function, **options)
45
+ else
46
+ new(type, fn: function, **options)
47
+ end
34
48
  end
35
49
 
36
- # @return [Class]
37
- def primitive
38
- type.primitive
50
+ # @api private
51
+ def self.wrapper_type
52
+ @wrapper_type ||= begin
53
+ if self < Wrapper
54
+ self
55
+ else
56
+ const_set(:Wrapping, ::Class.new(self).include(Wrapper))
57
+ end
58
+ end
39
59
  end
40
60
 
41
- # @return [String]
42
- def name
43
- type.name
61
+ # Instantiate a new constructor type instance
62
+ #
63
+ # @param [Type] type
64
+ # @param [Function] fn
65
+ # @param [Hash] options
66
+ #
67
+ # @api private
68
+ def initialize(type, fn: nil, **options)
69
+ @type = type
70
+ @fn = fn
71
+
72
+ super(type, **options, fn: fn)
44
73
  end
45
74
 
46
- # @return [Boolean]
47
- def default?
48
- type.default?
75
+ # @return [Object]
76
+ #
77
+ # @api private
78
+ def call_safe(input)
79
+ coerced = fn.(input) { |output = input| return yield(output) }
80
+ type.call_safe(coerced) { |output = coerced| yield(output) }
49
81
  end
50
82
 
51
- # @param [Object] input
52
83
  # @return [Object]
53
- def call(input)
54
- type[fn[input]]
84
+ #
85
+ # @api private
86
+ def call_unsafe(input)
87
+ type.call_unsafe(fn.(input))
55
88
  end
56
- alias_method :[], :call
57
89
 
58
90
  # @param [Object] input
59
91
  # @param [#call,nil] block
92
+ #
60
93
  # @return [Logic::Result, Types::Result]
61
94
  # @return [Object] if block given and try fails
95
+ #
96
+ # @api public
62
97
  def try(input, &block)
63
- type.try(fn[input], &block)
64
- rescue TypeError, ArgumentError => e
65
- failure(input, e.message)
98
+ value = fn.(input)
99
+ rescue CoercionError => e
100
+ failure = failure(input, e)
101
+ block_given? ? yield(failure) : failure
102
+ else
103
+ type.try(value, &block)
66
104
  end
67
105
 
106
+ # Build a new constructor by appending a block to the coercion function
107
+ #
68
108
  # @param [#call, nil] new_fn
69
109
  # @param [Hash] options
70
110
  # @param [#call, nil] block
111
+ #
71
112
  # @return [Constructor]
113
+ #
114
+ # @api public
72
115
  def constructor(new_fn = nil, **options, &block)
73
- left = new_fn || block
74
- right = fn
116
+ next_fn = Function[new_fn || block]
75
117
 
76
- with(**options, fn: -> input { left[right[input]] })
118
+ if next_fn.wrapper?
119
+ self.class.wrapper_type.new(with(**options), fn: next_fn)
120
+ else
121
+ with(**options, fn: fn >> next_fn)
122
+ end
77
123
  end
78
124
  alias_method :append, :constructor
79
125
  alias_method :>>, :constructor
80
126
 
81
- # @param [Object] value
82
- # @return [Boolean]
83
- def valid?(value)
84
- constructed_value = fn[value]
85
- rescue NoMethodError, TypeError, ArgumentError
86
- false
87
- else
88
- type.valid?(constructed_value)
89
- end
90
- alias_method :===, :valid?
91
-
92
127
  # @return [Class]
128
+ #
129
+ # @api private
93
130
  def constrained_type
94
131
  Constrained::Coercible
95
132
  end
96
133
 
97
- # @api public
98
- #
99
134
  # @see Nominal#to_ast
135
+ #
136
+ # @api public
100
137
  def to_ast(meta: true)
101
- [:constructor, [type.to_ast(meta: meta),
102
- register_fn(fn),
103
- meta ? self.meta : EMPTY_HASH]]
138
+ [:constructor, [type.to_ast(meta: meta), fn.to_ast]]
104
139
  end
105
140
 
106
- # @api public
141
+ # Build a new constructor by prepending a block to the coercion function
107
142
  #
108
143
  # @param [#call, nil] new_fn
109
144
  # @param [Hash] options
110
145
  # @param [#call, nil] block
146
+ #
111
147
  # @return [Constructor]
148
+ #
149
+ # @api public
112
150
  def prepend(new_fn = nil, **options, &block)
113
- left = new_fn || block
114
- right = fn
115
-
116
- with(**options, fn: -> input { right[left[input]] })
151
+ with(**options, fn: fn << (new_fn || block))
117
152
  end
118
153
  alias_method :<<, :prepend
119
154
 
120
- private
155
+ # Build a lax type
156
+ #
157
+ # @return [Lax]
158
+ # @api public
159
+ def lax
160
+ Lax.new(constructor_type[type.lax, **options])
161
+ end
121
162
 
122
- def register_fn(fn)
123
- Dry::Types::FnContainer.register(fn)
163
+ # Wrap the type with a proc
164
+ #
165
+ # @return [Proc]
166
+ #
167
+ # @api public
168
+ def to_proc
169
+ proc { |value| self.(value) }
124
170
  end
125
171
 
172
+ private
173
+
126
174
  # @param [Symbol] meth
127
175
  # @param [Boolean] include_private
128
176
  # @return [Boolean]
177
+ #
178
+ # @api private
129
179
  def respond_to_missing?(meth, include_private = false)
130
180
  super || type.respond_to?(meth)
131
181
  end
132
182
 
133
183
  # Delegates missing methods to {#type}
184
+ #
134
185
  # @param [Symbol] method
135
186
  # @param [Array] args
136
187
  # @param [#call, nil] block
188
+ #
189
+ # @api private
137
190
  def method_missing(method, *args, &block)
138
191
  if type.respond_to?(method)
139
- response = type.__send__(method, *args, &block)
192
+ response = type.public_send(method, *args, &block)
140
193
 
141
- if composable?(response)
142
- response.constructor_type.new(response, options)
194
+ if response.is_a?(Type) && type.class.equal?(response.class)
195
+ response.constructor_type[response, **options]
143
196
  else
144
197
  response
145
198
  end
@@ -147,10 +200,6 @@ module Dry
147
200
  super
148
201
  end
149
202
  end
150
-
151
- def composable?(value)
152
- value.kind_of?(Builder)
153
- end
154
203
  end
155
204
  end
156
205
  end
@@ -1,7 +1,12 @@
1
- require 'dry/container'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/container"
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Internal container for the built-in types
8
+ #
9
+ # @api private
5
10
  class Container
6
11
  include Dry::Container::Mixin
7
12
  end
@@ -1,9 +1,11 @@
1
- require 'dry/types/any'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/any"
2
4
 
3
5
  module Dry
4
6
  module Types
5
7
  # Primitives with {Kernel} coercion methods
6
- COERCIBLE = {
8
+ KERNEL_COERCIBLE = {
7
9
  string: String,
8
10
  integer: Integer,
9
11
  float: Float,
@@ -12,10 +14,19 @@ module Dry
12
14
  hash: ::Hash
13
15
  }.freeze
14
16
 
15
- # Primitives that are non-coercible through {Kernel} methods
17
+ # Primitives with coercions through by convention `to_*` methods
18
+ METHOD_COERCIBLE = {
19
+ symbol: Symbol
20
+ }.freeze
21
+
22
+ # By convention methods to coerce {METHOD_COERCIBLE} primitives
23
+ METHOD_COERCIBLE_METHODS = {
24
+ symbol: :to_sym
25
+ }.freeze
26
+
27
+ # Primitives that are non-coercible
16
28
  NON_COERCIBLE = {
17
29
  nil: NilClass,
18
- symbol: Symbol,
19
30
  class: Class,
20
31
  true: TrueClass,
21
32
  false: FalseClass,
@@ -26,7 +37,10 @@ module Dry
26
37
  }.freeze
27
38
 
28
39
  # All built-in primitives
29
- ALL_PRIMITIVES = COERCIBLE.merge(NON_COERCIBLE).freeze
40
+ ALL_PRIMITIVES = [KERNEL_COERCIBLE, METHOD_COERCIBLE, NON_COERCIBLE].reduce(&:merge).freeze
41
+
42
+ # All coercible types
43
+ COERCIBLE = KERNEL_COERCIBLE.merge(METHOD_COERCIBLE).freeze
30
44
 
31
45
  # All built-in primitives except {NilClass}
32
46
  NON_NIL = ALL_PRIMITIVES.reject { |name, _| name == :nil }.freeze
@@ -44,14 +58,23 @@ module Dry
44
58
  register("strict.#{name}", type)
45
59
  end
46
60
 
47
- # Register {COERCIBLE} types
48
- COERCIBLE.each do |name, primitive|
61
+ # Register {KERNEL_COERCIBLE} types
62
+ KERNEL_COERCIBLE.each do |name, primitive|
49
63
  register("coercible.#{name}", self["nominal.#{name}"].constructor(Kernel.method(primitive.name)))
50
64
  end
51
65
 
66
+ # Register {METHOD_COERCIBLE} types
67
+ METHOD_COERCIBLE.each_key do |name|
68
+ register(
69
+ "coercible.#{name}", self["nominal.#{name}"].constructor(&METHOD_COERCIBLE_METHODS[name])
70
+ )
71
+ end
72
+
52
73
  # Register optional strict {NON_NIL} types
53
74
  NON_NIL.each_key do |name|
54
- register("optional.strict.#{name}", self["strict.#{name}"].optional)
75
+ type = self[name.to_s].optional
76
+ register("optional.strict.#{name}", type)
77
+ register("optional.#{name}", type)
55
78
  end
56
79
 
57
80
  # Register optional {COERCIBLE} types
@@ -71,6 +94,6 @@ module Dry
71
94
  end
72
95
  end
73
96
 
74
- require 'dry/types/coercions'
75
- require 'dry/types/params'
76
- require 'dry/types/json'
97
+ require "dry/types/coercions"
98
+ require "dry/types/params"
99
+ require "dry/types/json"
@@ -1,7 +1,12 @@
1
- require 'dry/types/options'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/options"
2
4
 
3
5
  module Dry
4
6
  module Types
7
+ # Common API for types
8
+ #
9
+ # @api public
5
10
  module Decorator
6
11
  include Options
7
12
 
@@ -9,63 +14,76 @@ module Dry
9
14
  attr_reader :type
10
15
 
11
16
  # @param [Type] type
12
- def initialize(type, *)
17
+ def initialize(type, *, **)
13
18
  super
14
19
  @type = type
15
20
  end
16
21
 
17
22
  # @param [Object] input
18
23
  # @param [#call, nil] block
24
+ #
19
25
  # @return [Result,Logic::Result]
20
26
  # @return [Object] if block given and try fails
27
+ #
28
+ # @api public
21
29
  def try(input, &block)
22
30
  type.try(input, &block)
23
31
  end
24
32
 
25
- # @param [Object] value
26
- # @return [Boolean]
27
- def valid?(value)
28
- type.valid?(value)
29
- end
30
- alias_method :===, :valid?
31
-
32
33
  # @return [Boolean]
34
+ #
35
+ # @api public
33
36
  def default?
34
37
  type.default?
35
38
  end
36
39
 
37
40
  # @return [Boolean]
41
+ #
42
+ # @api public
38
43
  def constrained?
39
44
  type.constrained?
40
45
  end
41
46
 
42
- # @return [Sum]
43
- def optional
44
- Types['strict.nil'] | self
45
- end
46
-
47
47
  # @param [Symbol] meth
48
48
  # @param [Boolean] include_private
49
+ #
49
50
  # @return [Boolean]
51
+ #
52
+ # @api public
50
53
  def respond_to_missing?(meth, include_private = false)
51
54
  super || type.respond_to?(meth)
52
55
  end
53
56
 
57
+ # Wrap the type with a proc
58
+ #
59
+ # @return [Proc]
60
+ #
61
+ # @api public
62
+ def to_proc
63
+ proc { |value| self.(value) }
64
+ end
65
+
54
66
  private
55
67
 
56
68
  # @param [Object] response
69
+ #
57
70
  # @return [Boolean]
71
+ #
72
+ # @api private
58
73
  def decorate?(response)
59
- response.kind_of?(type.class)
74
+ response.is_a?(type.class)
60
75
  end
61
76
 
62
77
  # Delegates missing methods to {#type}
78
+ #
63
79
  # @param [Symbol] meth
64
80
  # @param [Array] args
65
81
  # @param [#call, nil] block
82
+ #
83
+ # @api private
66
84
  def method_missing(meth, *args, &block)
67
85
  if type.respond_to?(meth)
68
- response = type.__send__(meth, *args, &block)
86
+ response = type.public_send(meth, *args, &block)
69
87
 
70
88
  if decorate?(response)
71
89
  __new__(response)
@@ -76,10 +94,13 @@ module Dry
76
94
  super
77
95
  end
78
96
  end
97
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
79
98
 
80
99
  # Replace underlying type
100
+ #
101
+ # @api private
81
102
  def __new__(type)
82
- self.class.new(type, options)
103
+ self.class.new(type, *@__args__.drop(1), **@options)
83
104
  end
84
105
  end
85
106
  end
@@ -1,31 +1,46 @@
1
- require 'dry/types/decorator'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+ require "dry/types/decorator"
2
5
 
3
6
  module Dry
4
7
  module Types
8
+ # Default types are useful when a missing value should be replaced by a default one
9
+ #
10
+ # @api public
5
11
  class Default
6
- include Type
7
- include Decorator
8
- include Builder
9
- include Printable
10
- include Dry::Equalizer(:type, :options, :value, inspect: false)
11
-
12
+ # @api private
12
13
  class Callable < Default
13
- include Dry::Equalizer(:type, :options, inspect: false)
14
+ include Dry::Equalizer(:type, inspect: false, immutable: true)
14
15
 
15
16
  # Evaluates given callable
16
17
  # @return [Object]
17
18
  def evaluate
18
19
  value.call(type)
19
20
  end
21
+
22
+ # @return [true]
23
+ def callable?
24
+ true
25
+ end
20
26
  end
21
27
 
28
+ include Type
29
+ include Decorator
30
+ include Builder
31
+ include Printable
32
+ include Dry::Equalizer(:type, :value, inspect: false, immutable: true)
33
+
22
34
  # @return [Object]
23
35
  attr_reader :value
24
36
 
25
37
  alias_method :evaluate, :value
26
38
 
27
39
  # @param [Object, #call] value
40
+ #
28
41
  # @return [Class] {Default} or {Default::Callable}
42
+ #
43
+ # @api private
29
44
  def self.[](value)
30
45
  if value.respond_to?(:call)
31
46
  Callable
@@ -36,48 +51,78 @@ module Dry
36
51
 
37
52
  # @param [Type] type
38
53
  # @param [Object] value
54
+ #
55
+ # @api private
39
56
  def initialize(type, value, **options)
40
57
  super
41
58
  @value = value
42
59
  end
43
60
 
61
+ # Build a constrained type
62
+ #
44
63
  # @param [Array] args see {Dry::Types::Builder#constrained}
64
+ #
45
65
  # @return [Default]
66
+ #
67
+ # @api public
46
68
  def constrained(*args)
47
69
  type.constrained(*args).default(value)
48
70
  end
49
71
 
50
72
  # @return [true]
73
+ #
74
+ # @api public
51
75
  def default?
52
76
  true
53
77
  end
54
78
 
55
79
  # @param [Object] input
80
+ #
56
81
  # @return [Result::Success]
82
+ #
83
+ # @api public
57
84
  def try(input)
58
85
  success(call(input))
59
86
  end
60
87
 
88
+ # @return [Boolean]
89
+ #
90
+ # @api public
61
91
  def valid?(value = Undefined)
62
- value.equal?(Undefined) || super
92
+ Undefined.equal?(value) || super
63
93
  end
64
94
 
65
95
  # @param [Object] input
96
+ #
66
97
  # @return [Object] value passed through {#type} or {#default} value
67
- def call(input = Undefined)
98
+ #
99
+ # @api private
100
+ def call_unsafe(input = Undefined)
68
101
  if input.equal?(Undefined)
69
102
  evaluate
70
103
  else
71
- Undefined.default(type[input]) { evaluate }
104
+ Undefined.default(type.call_unsafe(input)) { evaluate }
72
105
  end
73
106
  end
74
- alias_method :[], :call
75
107
 
76
- private
108
+ # @param [Object] input
109
+ #
110
+ # @return [Object] value passed through {#type} or {#default} value
111
+ #
112
+ # @api private
113
+ def call_safe(input = Undefined, &block)
114
+ if input.equal?(Undefined)
115
+ evaluate
116
+ else
117
+ Undefined.default(type.call_safe(input, &block)) { evaluate }
118
+ end
119
+ end
77
120
 
78
- # Replace underlying type
79
- def __new__(type)
80
- self.class.new(type, value, options)
121
+ # @return [false]
122
+ #
123
+ # @api private
124
+ def callable?
125
+ false
81
126
  end
82
127
  end
83
128
  end