dry-types 0.13.2 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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"