dry-types 0.13.2 → 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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +763 -233
  3. data/LICENSE +17 -17
  4. data/README.md +15 -13
  5. data/dry-types.gemspec +28 -28
  6. data/lib/dry-types.rb +3 -1
  7. data/lib/dry/types.rb +156 -76
  8. data/lib/dry/types/any.rb +32 -12
  9. data/lib/dry/types/array.rb +19 -6
  10. data/lib/dry/types/array/constructor.rb +32 -0
  11. data/lib/dry/types/array/member.rb +75 -16
  12. data/lib/dry/types/builder.rb +131 -15
  13. data/lib/dry/types/builder_methods.rb +49 -20
  14. data/lib/dry/types/coercions.rb +76 -22
  15. data/lib/dry/types/coercions/json.rb +43 -7
  16. data/lib/dry/types/coercions/params.rb +118 -31
  17. data/lib/dry/types/compat.rb +0 -2
  18. data/lib/dry/types/compiler.rb +56 -41
  19. data/lib/dry/types/constrained.rb +81 -32
  20. data/lib/dry/types/constrained/coercible.rb +36 -6
  21. data/lib/dry/types/constraints.rb +18 -4
  22. data/lib/dry/types/constructor.rb +127 -54
  23. data/lib/dry/types/constructor/function.rb +216 -0
  24. data/lib/dry/types/constructor/wrapper.rb +94 -0
  25. data/lib/dry/types/container.rb +7 -0
  26. data/lib/dry/types/core.rb +54 -21
  27. data/lib/dry/types/decorator.rb +38 -17
  28. data/lib/dry/types/default.rb +61 -16
  29. data/lib/dry/types/enum.rb +43 -20
  30. data/lib/dry/types/errors.rb +75 -9
  31. data/lib/dry/types/extensions.rb +7 -1
  32. data/lib/dry/types/extensions/maybe.rb +74 -16
  33. data/lib/dry/types/extensions/monads.rb +29 -0
  34. data/lib/dry/types/fn_container.rb +6 -1
  35. data/lib/dry/types/hash.rb +86 -67
  36. data/lib/dry/types/hash/constructor.rb +33 -0
  37. data/lib/dry/types/inflector.rb +3 -1
  38. data/lib/dry/types/json.rb +18 -16
  39. data/lib/dry/types/lax.rb +75 -0
  40. data/lib/dry/types/map.rb +76 -33
  41. data/lib/dry/types/meta.rb +51 -0
  42. data/lib/dry/types/module.rb +120 -0
  43. data/lib/dry/types/nominal.rb +210 -0
  44. data/lib/dry/types/options.rb +13 -26
  45. data/lib/dry/types/params.rb +39 -25
  46. data/lib/dry/types/predicate_inferrer.rb +238 -0
  47. data/lib/dry/types/predicate_registry.rb +34 -0
  48. data/lib/dry/types/primitive_inferrer.rb +97 -0
  49. data/lib/dry/types/printable.rb +16 -0
  50. data/lib/dry/types/printer.rb +315 -0
  51. data/lib/dry/types/result.rb +29 -3
  52. data/lib/dry/types/schema.rb +408 -0
  53. data/lib/dry/types/schema/key.rb +156 -0
  54. data/lib/dry/types/spec/types.rb +103 -33
  55. data/lib/dry/types/sum.rb +84 -35
  56. data/lib/dry/types/type.rb +49 -0
  57. data/lib/dry/types/version.rb +3 -1
  58. metadata +68 -79
  59. data/.gitignore +0 -10
  60. data/.rspec +0 -2
  61. data/.travis.yml +0 -29
  62. data/.yardopts +0 -5
  63. data/CONTRIBUTING.md +0 -29
  64. data/Gemfile +0 -24
  65. data/Rakefile +0 -20
  66. data/benchmarks/hash_schemas.rb +0 -51
  67. data/lib/dry/types/compat/form_types.rb +0 -27
  68. data/lib/dry/types/compat/int.rb +0 -14
  69. data/lib/dry/types/definition.rb +0 -113
  70. data/lib/dry/types/hash/schema.rb +0 -199
  71. data/lib/dry/types/hash/schema_builder.rb +0 -75
  72. data/lib/dry/types/safe.rb +0 -59
data/lib/dry/types/any.rb CHANGED
@@ -1,27 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Types
3
- Any = Class.new(Definition) do
5
+ # Any is a nominal type that defines Object as the primitive class
6
+ #
7
+ # This type is useful in places where you can't be specific about the type
8
+ # and anything is acceptable.
9
+ #
10
+ # @api public
11
+ class AnyClass < Nominal
12
+ def self.name
13
+ "Any"
14
+ end
15
+
16
+ # @api private
4
17
  def initialize(**options)
5
- super(::Object, options)
18
+ super(::Object, **options)
6
19
  end
7
20
 
8
21
  # @return [String]
22
+ #
23
+ # @api public
9
24
  def name
10
- 'Any'
11
- end
12
-
13
- # @param [Object] any input is valid
14
- # @return [true]
15
- def valid?(_)
16
- true
25
+ "Any"
17
26
  end
18
- alias_method :===, :valid?
19
27
 
20
28
  # @param [Hash] new_options
29
+ #
21
30
  # @return [Type]
22
- def with(new_options)
31
+ #
32
+ # @api public
33
+ def with(**new_options)
23
34
  self.class.new(**options, meta: @meta, **new_options)
24
35
  end
25
- end.new
36
+
37
+ # @return [Array]
38
+ #
39
+ # @api public
40
+ def to_ast(meta: true)
41
+ [:any, meta ? self.meta : EMPTY_HASH]
42
+ end
43
+ end
44
+
45
+ Any = AnyClass.new
26
46
  end
27
47
  end
@@ -1,13 +1,21 @@
1
- require 'dry/types/array/member'
2
- require 'dry/core/deprecations'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/array/member"
4
+ require "dry/types/array/constructor"
3
5
 
4
6
  module Dry
5
7
  module Types
6
- class Array < Definition
7
- extend Dry::Core::Deprecations[:'dry-types']
8
-
9
- # @param [Type] type
8
+ # Array type can be used to define an array with optional member type
9
+ #
10
+ # @api public
11
+ class Array < Nominal
12
+ # Build an array type with a member type
13
+ #
14
+ # @param [Type,#call] type
15
+ #
10
16
  # @return [Array::Member]
17
+ #
18
+ # @api public
11
19
  def of(type)
12
20
  member =
13
21
  case type
@@ -17,6 +25,11 @@ module Dry
17
25
 
18
26
  Array::Member.new(primitive, **options, member: member)
19
27
  end
28
+
29
+ # @api private
30
+ def constructor_type
31
+ ::Dry::Types::Array::Constructor
32
+ end
20
33
  end
21
34
  end
22
35
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/constructor"
4
+
5
+ module Dry
6
+ module Types
7
+ # @api public
8
+ class Array < Nominal
9
+ # @api private
10
+ class Constructor < ::Dry::Types::Constructor
11
+ # @api private
12
+ def constructor_type
13
+ ::Dry::Types::Array::Constructor
14
+ end
15
+
16
+ # @return [Lax]
17
+ #
18
+ # @api public
19
+ def lax
20
+ Lax.new(type.lax.constructor(fn, meta: meta))
21
+ end
22
+
23
+ # @see Dry::Types::Array#of
24
+ #
25
+ # @api public
26
+ def of(member)
27
+ type.of(member).constructor(fn, meta: meta)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,57 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/types/array/constructor"
4
+
1
5
  module Dry
2
6
  module Types
3
- class Array < Definition
7
+ class Array < Nominal
8
+ # Member arrays define their member type that is applied to each element
9
+ #
10
+ # @api public
4
11
  class Member < Array
5
12
  # @return [Type]
6
13
  attr_reader :member
7
14
 
8
15
  # @param [Class] primitive
9
16
  # @param [Hash] options
17
+ #
10
18
  # @option options [Type] :member
11
- def initialize(primitive, options = {})
19
+ #
20
+ # @api private
21
+ def initialize(primitive, **options)
12
22
  @member = options.fetch(:member)
13
23
  super
14
24
  end
15
25
 
16
26
  # @param [Object] input
17
- # @param [Symbol] meth
27
+ #
18
28
  # @return [Array]
19
- def call(input, meth = :call)
20
- input.map { |el| member.__send__(meth, el) }
29
+ #
30
+ # @api private
31
+ def call_unsafe(input)
32
+ if primitive?(input)
33
+ input.each_with_object([]) do |el, output|
34
+ coerced = member.call_unsafe(el)
35
+
36
+ output << coerced unless Undefined.equal?(coerced)
37
+ end
38
+ else
39
+ super
40
+ end
21
41
  end
22
- alias_method :[], :call
23
42
 
24
- # @param [Array, #all?, Object] value
25
- # @return [Boolean]
26
- def valid?(value)
27
- super && value.all? { |el| member.valid?(el) }
43
+ # @param [Object] input
44
+ # @return [Array]
45
+ #
46
+ # @api private
47
+ def call_safe(input)
48
+ if primitive?(input)
49
+ failed = false
50
+
51
+ result = input.each_with_object([]) do |el, output|
52
+ coerced = member.call_safe(el) { |out = el|
53
+ failed = true
54
+ out
55
+ }
56
+
57
+ output << coerced unless Undefined.equal?(coerced)
58
+ end
59
+
60
+ failed ? yield(result) : result
61
+ else
62
+ yield
63
+ end
28
64
  end
29
65
 
30
66
  # @param [Array, Object] input
31
67
  # @param [#call,nil] block
68
+ #
32
69
  # @yieldparam [Failure] failure
33
70
  # @yieldreturn [Result]
71
+ #
34
72
  # @return [Result,Logic::Result]
73
+ #
74
+ # @api public
35
75
  def try(input, &block)
36
- if input.is_a?(::Array)
37
- result = call(input, :try).reject { |r| r.input.equal?(Undefined) }
38
- output = result.map(&:input)
76
+ if primitive?(input)
77
+ output = []
78
+
79
+ result = input.map { |el| member.try(el) }
80
+ result.each do |r|
81
+ output << r.input unless Undefined.equal?(r.input)
82
+ end
39
83
 
40
84
  if result.all?(&:success?)
41
85
  success(output)
42
86
  else
43
- failure = failure(output, result.select(&:failure?))
87
+ error = result.find(&:failure?).error
88
+ failure = failure(output, error)
44
89
  block ? yield(failure) : failure
45
90
  end
46
91
  else
47
- failure = failure(input, "#{input} is not an array")
92
+ failure = failure(input, CoercionError.new("#{input} is not an array"))
48
93
  block ? yield(failure) : failure
49
94
  end
50
95
  end
51
96
 
97
+ # Build a lax type
98
+ #
99
+ # @return [Lax]
100
+ #
52
101
  # @api public
102
+ def lax
103
+ Lax.new(Member.new(primitive, **options, member: member.lax, meta: meta))
104
+ end
105
+
106
+ # @see Nominal#to_ast
53
107
  #
54
- # @see Definition#to_ast
108
+ # @api public
55
109
  def to_ast(meta: true)
56
110
  if member.respond_to?(:to_ast)
57
111
  [:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
@@ -59,6 +113,11 @@ module Dry
59
113
  [:array, [member, meta ? self.meta : EMPTY_HASH]]
60
114
  end
61
115
  end
116
+
117
+ # @api private
118
+ def constructor_type
119
+ ::Dry::Types::Array::Constructor
120
+ end
62
121
  end
63
122
  end
64
123
  end
@@ -1,47 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/deprecations"
4
+
1
5
  module Dry
2
6
  module Types
7
+ # Common API for building types and composition
8
+ #
9
+ # @api public
3
10
  module Builder
4
11
  include Dry::Core::Constants
5
12
 
6
13
  # @return [Class]
14
+ #
15
+ # @api private
7
16
  def constrained_type
8
17
  Constrained
9
18
  end
10
19
 
20
+ # @return [Class]
21
+ #
22
+ # @api private
23
+ def constructor_type
24
+ Constructor
25
+ end
26
+
27
+ # Compose two types into a Sum type
28
+ #
11
29
  # @param [Type] other
30
+ #
12
31
  # @return [Sum, Sum::Constrained]
32
+ #
33
+ # @api private
13
34
  def |(other)
14
35
  klass = constrained? && other.constrained? ? Sum::Constrained : Sum
15
36
  klass.new(self, other)
16
37
  end
17
38
 
39
+ # Turn a type into an optional type
40
+ #
18
41
  # @return [Sum]
42
+ #
43
+ # @api public
19
44
  def optional
20
- Types['strict.nil'] | self
45
+ Types["nil"] | self
21
46
  end
22
47
 
48
+ # Turn a type into a constrained type
49
+ #
23
50
  # @param [Hash] options constraining rule (see {Types.Rule})
51
+ #
24
52
  # @return [Constrained]
53
+ #
54
+ # @api public
25
55
  def constrained(options)
26
56
  constrained_type.new(self, rule: Types.Rule(options))
27
57
  end
28
58
 
59
+ # Turn a type into a type with a default value
60
+ #
29
61
  # @param [Object] input
62
+ # @option [Boolean] shared Whether it's safe to share the value across type applications
30
63
  # @param [#call,nil] block
64
+ #
31
65
  # @raise [ConstraintError]
66
+ #
32
67
  # @return [Default]
33
- def default(input = Undefined, &block)
34
- value = input.equal?(Undefined) ? block : input
68
+ #
69
+ # @api public
70
+ def default(input = Undefined, options = EMPTY_HASH, &block)
71
+ unless input.frozen? || options[:shared]
72
+ where = Core::Deprecations::STACK.()
73
+ Core::Deprecations.warn(
74
+ "#{input.inspect} is mutable."\
75
+ " Be careful: types will return the same instance of the default"\
76
+ " value every time. Call `.freeze` when setting the default"\
77
+ " or pass `shared: true` to discard this warning."\
78
+ "\n#{where}",
79
+ tag: :'dry-types'
80
+ )
81
+ end
82
+
83
+ value = Undefined.default(input, block)
84
+ type = Default[value].new(self, value)
35
85
 
36
- if value.is_a?(Proc) || valid?(value)
37
- Default[value].new(self, value)
86
+ if !type.callable? && !valid?(value)
87
+ raise ConstraintError.new(
88
+ "default value #{value.inspect} violates constraints",
89
+ value
90
+ )
38
91
  else
39
- raise ConstraintError.new("default value #{value.inspect} violates constraints", value)
92
+ type
40
93
  end
41
94
  end
42
95
 
96
+ # Define an enum on top of the existing type
97
+ #
43
98
  # @param [Array] values
99
+ #
44
100
  # @return [Enum]
101
+ #
102
+ # @api public
45
103
  def enum(*values)
46
104
  mapping =
47
105
  if values.length == 1 && values[0].is_a?(::Hash)
@@ -53,24 +111,82 @@ module Dry
53
111
  Enum.new(constrained(included_in: mapping.keys), mapping: mapping)
54
112
  end
55
113
 
56
- # @return [Safe]
57
- def safe
58
- Safe.new(self)
114
+ # Turn a type into a lax type that will rescue from type-errors and
115
+ # return the original input
116
+ #
117
+ # @return [Lax]
118
+ #
119
+ # @api public
120
+ def lax
121
+ Lax.new(self)
59
122
  end
60
123
 
124
+ # Define a constructor for the type
125
+ #
61
126
  # @param [#call,nil] constructor
62
127
  # @param [Hash] options
63
128
  # @param [#call,nil] block
129
+ #
64
130
  # @return [Constructor]
131
+ #
132
+ # @api public
65
133
  def constructor(constructor = nil, **options, &block)
66
- Constructor.new(with(options), fn: constructor || block)
134
+ constructor_type[with(**options), fn: constructor || block]
135
+ end
136
+ alias_method :append, :constructor
137
+ alias_method :prepend, :constructor
138
+ alias_method :>>, :constructor
139
+ alias_method :<<, :constructor
140
+
141
+ # Use the given value on type mismatch
142
+ #
143
+ # @param [Object] value
144
+ # @option [Boolean] shared Whether it's safe to share the value across type applications
145
+ # @param [#call,nil] fallback
146
+ #
147
+ # @return [Constructor]
148
+ #
149
+ # @api public
150
+ def fallback(value = Undefined, shared: false, &_fallback)
151
+ if Undefined.equal?(value) && !block_given?
152
+ raise ::ArgumentError, "fallback value or a block must be given"
153
+ end
154
+
155
+ if !block_given? && !valid?(value)
156
+ raise ConstraintError.new(
157
+ "fallback value #{value.inspect} violates constraints",
158
+ value
159
+ )
160
+ end
161
+
162
+ unless value.frozen? || shared
163
+ where = Core::Deprecations::STACK.()
164
+ Core::Deprecations.warn(
165
+ "#{value.inspect} is mutable."\
166
+ " Be careful: types will return the same instance of the fallback"\
167
+ " value every time. Call `.freeze` when setting the fallback"\
168
+ " or pass `shared: true` to discard this warning."\
169
+ "\n#{where}",
170
+ tag: :'dry-types'
171
+ )
172
+ end
173
+
174
+ constructor do |input, type, &_block|
175
+ type.(input) do |output = input|
176
+ if block_given?
177
+ yield(output)
178
+ else
179
+ value
180
+ end
181
+ end
182
+ end
67
183
  end
68
184
  end
69
185
  end
70
186
  end
71
187
 
72
- require 'dry/types/default'
73
- require 'dry/types/constrained'
74
- require 'dry/types/enum'
75
- require 'dry/types/safe'
76
- require 'dry/types/sum'
188
+ require "dry/types/default"
189
+ require "dry/types/constrained"
190
+ require "dry/types/enum"
191
+ require "dry/types/lax"
192
+ require "dry/types/sum"