dry-types 1.0.1 → 1.2.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  3. data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
  4. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  5. data/.travis.yml +10 -4
  6. data/CHANGELOG.md +101 -3
  7. data/Gemfile +9 -6
  8. data/README.md +2 -2
  9. data/Rakefile +2 -2
  10. data/benchmarks/hash_schemas.rb +8 -6
  11. data/benchmarks/lax_schema.rb +0 -1
  12. data/benchmarks/profile_invalid_input.rb +1 -1
  13. data/benchmarks/profile_lax_schema_valid.rb +1 -1
  14. data/benchmarks/profile_valid_input.rb +1 -1
  15. data/docsite/source/array-with-member.html.md +13 -0
  16. data/docsite/source/built-in-types.html.md +116 -0
  17. data/docsite/source/constraints.html.md +31 -0
  18. data/docsite/source/custom-types.html.md +93 -0
  19. data/docsite/source/default-values.html.md +91 -0
  20. data/docsite/source/enum.html.md +69 -0
  21. data/docsite/source/getting-started.html.md +57 -0
  22. data/docsite/source/hash-schemas.html.md +169 -0
  23. data/docsite/source/index.html.md +155 -0
  24. data/docsite/source/map.html.md +17 -0
  25. data/docsite/source/optional-values.html.md +96 -0
  26. data/docsite/source/sum.html.md +21 -0
  27. data/dry-types.gemspec +19 -19
  28. data/lib/dry/types.rb +9 -4
  29. data/lib/dry/types/array.rb +6 -0
  30. data/lib/dry/types/array/constructor.rb +32 -0
  31. data/lib/dry/types/array/member.rb +8 -1
  32. data/lib/dry/types/builder.rb +1 -1
  33. data/lib/dry/types/builder_methods.rb +33 -23
  34. data/lib/dry/types/coercions.rb +19 -6
  35. data/lib/dry/types/coercions/params.rb +4 -4
  36. data/lib/dry/types/constrained.rb +5 -0
  37. data/lib/dry/types/constructor.rb +5 -37
  38. data/lib/dry/types/constructor/function.rb +4 -5
  39. data/lib/dry/types/core.rb +27 -8
  40. data/lib/dry/types/decorator.rb +1 -1
  41. data/lib/dry/types/enum.rb +1 -0
  42. data/lib/dry/types/extensions.rb +4 -0
  43. data/lib/dry/types/extensions/maybe.rb +9 -1
  44. data/lib/dry/types/extensions/monads.rb +29 -0
  45. data/lib/dry/types/hash.rb +10 -11
  46. data/lib/dry/types/hash/constructor.rb +5 -5
  47. data/lib/dry/types/json.rb +4 -0
  48. data/lib/dry/types/lax.rb +4 -4
  49. data/lib/dry/types/map.rb +8 -4
  50. data/lib/dry/types/module.rb +3 -3
  51. data/lib/dry/types/nominal.rb +3 -4
  52. data/lib/dry/types/params.rb +9 -0
  53. data/lib/dry/types/predicate_inferrer.rb +197 -0
  54. data/lib/dry/types/predicate_registry.rb +34 -0
  55. data/lib/dry/types/primitive_inferrer.rb +97 -0
  56. data/lib/dry/types/printer.rb +17 -12
  57. data/lib/dry/types/schema.rb +14 -20
  58. data/lib/dry/types/schema/key.rb +19 -1
  59. data/lib/dry/types/spec/types.rb +3 -6
  60. data/lib/dry/types/version.rb +1 -1
  61. metadata +79 -52
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'dry/types/array/constructor'
4
+
3
5
  module Dry
4
6
  module Types
5
7
  class Array < Nominal
@@ -98,7 +100,7 @@ module Dry
98
100
  #
99
101
  # @api public
100
102
  def lax
101
- Lax.new(Member.new(primitive, { **options, member: member.lax }))
103
+ Lax.new(Member.new(primitive, { **options, member: member.lax, meta: meta }))
102
104
  end
103
105
 
104
106
  # @see Nominal#to_ast
@@ -111,6 +113,11 @@ module Dry
111
113
  [:array, [member, meta ? self.meta : EMPTY_HASH]]
112
114
  end
113
115
  end
116
+
117
+ # @api private
118
+ def constructor_type
119
+ ::Dry::Types::Array::Constructor
120
+ end
114
121
  end
115
122
  end
116
123
  end
@@ -75,7 +75,7 @@ module Dry
75
75
  ' Be careful: types will return the same instance of the default'\
76
76
  ' value every time. Call `.freeze` when setting the default'\
77
77
  ' or pass `shared: true` to discard this warning.'\
78
- "\n#{ where }",
78
+ "\n#{where}",
79
79
  tag: :'dry-types'
80
80
  )
81
81
  end
@@ -25,7 +25,7 @@ module Dry
25
25
  #
26
26
  # @return [Dry::Types::Array]
27
27
  def Array(type)
28
- self::Array.of(type)
28
+ Strict(::Array).of(type)
29
29
  end
30
30
 
31
31
  # Build a hash schema
@@ -34,7 +34,7 @@ module Dry
34
34
  #
35
35
  # @return [Dry::Types::Array]
36
36
  def Hash(type_map)
37
- self::Hash.schema(type_map)
37
+ Strict(::Hash).schema(type_map)
38
38
  end
39
39
 
40
40
  # Build a type which values are instances of a given class
@@ -49,7 +49,7 @@ module Dry
49
49
  #
50
50
  # @return [Dry::Types::Type]
51
51
  def Instance(klass)
52
- Nominal.new(klass).constrained(type: klass)
52
+ Nominal(klass).constrained(type: klass)
53
53
  end
54
54
  alias_method :Strict, :Instance
55
55
 
@@ -60,7 +60,7 @@ module Dry
60
60
  #
61
61
  # @return [Dry::Types::Type]
62
62
  def Value(value)
63
- Nominal.new(value.class).constrained(eql: value)
63
+ Nominal(value.class).constrained(eql: value)
64
64
  end
65
65
 
66
66
  # Build a type with a single value
@@ -70,7 +70,7 @@ module Dry
70
70
  #
71
71
  # @return [Dry::Types::Type]
72
72
  def Constant(object)
73
- Nominal.new(object.class).constrained(is: object)
73
+ Nominal(object.class).constrained(is: object)
74
74
  end
75
75
 
76
76
  # Build a constructor type
@@ -82,7 +82,11 @@ module Dry
82
82
  #
83
83
  # @return [Dry::Types::Type]
84
84
  def Constructor(klass, cons = nil, &block)
85
- Nominal.new(klass).constructor(cons || block || klass.method(:new))
85
+ if klass.is_a?(Type)
86
+ klass.constructor(cons || block || klass.method(:new))
87
+ else
88
+ Nominal(klass).constructor(cons || block || klass.method(:new))
89
+ end
86
90
  end
87
91
 
88
92
  # Build a nominal type
@@ -91,7 +95,13 @@ module Dry
91
95
  #
92
96
  # @return [Dry::Types::Type]
93
97
  def Nominal(klass)
94
- Nominal.new(klass)
98
+ if klass <= ::Array
99
+ Array.new(klass)
100
+ elsif klass <= ::Hash
101
+ Hash.new(klass)
102
+ else
103
+ Nominal.new(klass)
104
+ end
95
105
  end
96
106
 
97
107
  # Build a map type
@@ -105,24 +115,24 @@ module Dry
105
115
  #
106
116
  # @return [Dry::Types::Map]
107
117
  def Map(key_type, value_type)
108
- Types['nominal.hash'].map(key_type, value_type)
118
+ Nominal(::Hash).map(key_type, value_type)
109
119
  end
110
120
 
111
- # # Builds a constrained nominal type accepting any value that
112
- # # responds to given methods
113
- # #
114
- # # @example
115
- # # Types::Callable = Types.Contract(:call)
116
- # # Types::Contact = Types.Contract(:name, :address)
117
- # #
118
- # # @param methods [Array<String, Symbol>] Method names
119
- # #
120
- # # @return [Dry::Types::Contrained]
121
- # def Contract(*methods)
122
- # methods.reduce(Types['nominal.any']) do |type, method|
123
- # type.constrained(respond_to: method)
124
- # end
125
- # end
121
+ # Builds a constrained nominal type accepting any value that
122
+ # responds to given methods
123
+ #
124
+ # @example
125
+ # Types::Callable = Types.Interface(:call)
126
+ # Types::Contact = Types.Interface(:name, :address)
127
+ #
128
+ # @param methods [Array<String, Symbol>] Method names
129
+ #
130
+ # @return [Dry::Types::Contrained]
131
+ def Interface(*methods)
132
+ methods.reduce(Types['nominal.any']) do |type, method|
133
+ type.constrained(respond_to: method)
134
+ end
135
+ end
126
136
  end
127
137
  end
128
138
  end
@@ -36,8 +36,8 @@ module Dry
36
36
  if input.respond_to?(:to_str)
37
37
  begin
38
38
  ::Date.parse(input)
39
- rescue ArgumentError, RangeError => error
40
- CoercionError.handle(error, &block)
39
+ rescue ArgumentError, RangeError => e
40
+ CoercionError.handle(e, &block)
41
41
  end
42
42
  elsif block_given?
43
43
  yield
@@ -57,8 +57,8 @@ module Dry
57
57
  if input.respond_to?(:to_str)
58
58
  begin
59
59
  ::DateTime.parse(input)
60
- rescue ArgumentError => error
61
- CoercionError.handle(error, &block)
60
+ rescue ArgumentError => e
61
+ CoercionError.handle(e, &block)
62
62
  end
63
63
  elsif block_given?
64
64
  yield
@@ -78,8 +78,8 @@ module Dry
78
78
  if input.respond_to?(:to_str)
79
79
  begin
80
80
  ::Time.parse(input)
81
- rescue ArgumentError => error
82
- CoercionError.handle(error, &block)
81
+ rescue ArgumentError => e
82
+ CoercionError.handle(e, &block)
83
83
  end
84
84
  elsif block_given?
85
85
  yield
@@ -88,6 +88,19 @@ module Dry
88
88
  end
89
89
  end
90
90
 
91
+ # @param [#to_sym, Object] input
92
+ #
93
+ # @return [Symbol, Object]
94
+ #
95
+ # @raise CoercionError
96
+ #
97
+ # @api public
98
+ def to_symbol(input, &block)
99
+ input.to_sym
100
+ rescue NoMethodError => e
101
+ CoercionError.handle(e, &block)
102
+ end
103
+
91
104
  private
92
105
 
93
106
  # Checks whether String is empty
@@ -71,8 +71,8 @@ module Dry
71
71
  else
72
72
  Integer(input)
73
73
  end
74
- rescue ArgumentError, TypeError => error
75
- CoercionError.handle(error, &block)
74
+ rescue ArgumentError, TypeError => e
75
+ CoercionError.handle(e, &block)
76
76
  end
77
77
 
78
78
  # @param [#to_f, Object] input
@@ -84,8 +84,8 @@ module Dry
84
84
  # @api public
85
85
  def self.to_float(input, &block)
86
86
  Float(input)
87
- rescue ArgumentError, TypeError => error
88
- CoercionError.handle(error, &block)
87
+ rescue ArgumentError, TypeError => e
88
+ CoercionError.handle(e, &block)
89
89
  end
90
90
 
91
91
  # @param [#to_d, Object] input
@@ -126,6 +126,11 @@ module Dry
126
126
  [:constrained, [type.to_ast(meta: meta), rule.to_ast]]
127
127
  end
128
128
 
129
+ # @api private
130
+ def constructor_type
131
+ type.constructor_type
132
+ end
133
+
129
134
  private
130
135
 
131
136
  # @param [Object] response
@@ -12,15 +12,13 @@ module Dry
12
12
  class Constructor < Nominal
13
13
  include Dry::Equalizer(:type, :options, inspect: false)
14
14
 
15
- private :meta
16
-
17
15
  # @return [#call]
18
16
  attr_reader :fn
19
17
 
20
18
  # @return [Type]
21
19
  attr_reader :type
22
20
 
23
- undef :constrained?
21
+ undef :constrained?, :meta, :optional?, :primitive, :default?, :name
24
22
 
25
23
  # @param [Builder, Object] input
26
24
  # @param [Hash] options
@@ -46,31 +44,6 @@ module Dry
46
44
  super(type, **options, fn: fn)
47
45
  end
48
46
 
49
- # Return the inner type's primitive
50
- #
51
- # @return [Class]
52
- #
53
- # @api public
54
- def primitive
55
- type.primitive
56
- end
57
-
58
- # Return the inner type's name
59
- #
60
- # @return [String]
61
- #
62
- # @api public
63
- def name
64
- type.name
65
- end
66
-
67
- # @return [Boolean]
68
- #
69
- # @api public
70
- def default?
71
- type.default?
72
- end
73
-
74
47
  # @return [Object]
75
48
  #
76
49
  # @api private
@@ -95,8 +68,8 @@ module Dry
95
68
  # @api public
96
69
  def try(input, &block)
97
70
  value = fn.(input)
98
- rescue CoercionError => error
99
- failure = failure(input, error)
71
+ rescue CoercionError => e
72
+ failure = failure(input, e)
100
73
  block_given? ? yield(failure) : failure
101
74
  else
102
75
  type.try(value, &block)
@@ -182,9 +155,9 @@ module Dry
182
155
  # @api private
183
156
  def method_missing(method, *args, &block)
184
157
  if type.respond_to?(method)
185
- response = type.__send__(method, *args, &block)
158
+ response = type.public_send(method, *args, &block)
186
159
 
187
- if composable?(response)
160
+ if response.is_a?(Type) && type.class == response.class
188
161
  response.constructor_type.new(response, options)
189
162
  else
190
163
  response
@@ -193,11 +166,6 @@ module Dry
193
166
  super
194
167
  end
195
168
  end
196
-
197
- # @api private
198
- def composable?(value)
199
- value.is_a?(Builder)
200
- end
201
169
  end
202
170
  end
203
171
  end
@@ -1,4 +1,3 @@
1
-
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'concurrent/map'
@@ -16,8 +15,8 @@ module Dry
16
15
  class Safe < Function
17
16
  def call(input, &block)
18
17
  @fn.(input, &block)
19
- rescue NoMethodError, TypeError, ArgumentError => error
20
- CoercionError.handle(error, &block)
18
+ rescue NoMethodError, TypeError, ArgumentError => e
19
+ CoercionError.handle(e, &block)
21
20
  end
22
21
  end
23
22
 
@@ -91,8 +90,8 @@ module Dry
91
90
  class PrivateSafeCall < PrivateCall
92
91
  def call(input, &block)
93
92
  @target.send(@name, input)
94
- rescue NoMethodError, TypeError, ArgumentError => error
95
- CoercionError.handle(error, &block)
93
+ rescue NoMethodError, TypeError, ArgumentError => e
94
+ CoercionError.handle(e, &block)
96
95
  end
97
96
  end
98
97
 
@@ -5,7 +5,7 @@ require 'dry/types/any'
5
5
  module Dry
6
6
  module Types
7
7
  # Primitives with {Kernel} coercion methods
8
- COERCIBLE = {
8
+ KERNEL_COERCIBLE = {
9
9
  string: String,
10
10
  integer: Integer,
11
11
  float: Float,
@@ -14,10 +14,19 @@ module Dry
14
14
  hash: ::Hash
15
15
  }.freeze
16
16
 
17
- # 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
18
28
  NON_COERCIBLE = {
19
29
  nil: NilClass,
20
- symbol: Symbol,
21
30
  class: Class,
22
31
  true: TrueClass,
23
32
  false: FalseClass,
@@ -28,7 +37,10 @@ module Dry
28
37
  }.freeze
29
38
 
30
39
  # All built-in primitives
31
- 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
32
44
 
33
45
  # All built-in primitives except {NilClass}
34
46
  NON_NIL = ALL_PRIMITIVES.reject { |name, _| name == :nil }.freeze
@@ -46,11 +58,18 @@ module Dry
46
58
  register("strict.#{name}", type)
47
59
  end
48
60
 
49
- # Register {COERCIBLE} types
50
- COERCIBLE.each do |name, primitive|
61
+ # Register {KERNEL_COERCIBLE} types
62
+ KERNEL_COERCIBLE.each do |name, primitive|
51
63
  register("coercible.#{name}", self["nominal.#{name}"].constructor(Kernel.method(primitive.name)))
52
64
  end
53
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
+
54
73
  # Register optional strict {NON_NIL} types
55
74
  NON_NIL.each_key do |name|
56
75
  register("optional.strict.#{name}", self["strict.#{name}"].optional)
@@ -64,8 +83,8 @@ module Dry
64
83
  # Register `:bool` since it's common and not a built-in Ruby type :(
65
84
  register('nominal.bool', self['nominal.true'] | self['nominal.false'])
66
85
  bool = self['strict.true'] | self['strict.false']
67
- register("strict.bool", bool)
68
- register("bool", bool)
86
+ register('strict.bool', bool)
87
+ register('bool', bool)
69
88
 
70
89
  register('any', Any)
71
90
  register('nominal.any', Any)
@@ -90,7 +90,7 @@ module Dry
90
90
  # @api private
91
91
  def method_missing(meth, *args, &block)
92
92
  if type.respond_to?(meth)
93
- response = type.__send__(meth, *args, &block)
93
+ response = type.public_send(meth, *args, &block)
94
94
 
95
95
  if decorate?(response)
96
96
  __new__(response)
@@ -11,6 +11,7 @@ module Dry
11
11
  include Type
12
12
  include Dry::Equalizer(:type, :mapping, inspect: false)
13
13
  include Decorator
14
+ include Builder
14
15
 
15
16
  # @return [Array]
16
17
  attr_reader :values
@@ -3,3 +3,7 @@
3
3
  Dry::Types.register_extension(:maybe) do
4
4
  require 'dry/types/extensions/maybe'
5
5
  end
6
+
7
+ Dry::Types.register_extension(:monads) do
8
+ require 'dry/types/extensions/monads'
9
+ end