dry-types 1.0.1 → 1.2.0

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