dry-types 1.0.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +2 -5
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/custom_ci.yml +76 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +34 -0
  9. data/.gitignore +1 -1
  10. data/.rspec +3 -1
  11. data/.rubocop.yml +89 -0
  12. data/CHANGELOG.md +127 -3
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +2 -2
  15. data/Gemfile +12 -6
  16. data/LICENSE +17 -17
  17. data/README.md +2 -2
  18. data/Rakefile +2 -2
  19. data/benchmarks/hash_schemas.rb +8 -6
  20. data/benchmarks/lax_schema.rb +0 -1
  21. data/benchmarks/profile_invalid_input.rb +1 -1
  22. data/benchmarks/profile_lax_schema_valid.rb +1 -1
  23. data/benchmarks/profile_valid_input.rb +1 -1
  24. data/docsite/source/array-with-member.html.md +13 -0
  25. data/docsite/source/built-in-types.html.md +116 -0
  26. data/docsite/source/constraints.html.md +31 -0
  27. data/docsite/source/custom-types.html.md +93 -0
  28. data/docsite/source/default-values.html.md +91 -0
  29. data/docsite/source/enum.html.md +69 -0
  30. data/docsite/source/extensions.html.md +15 -0
  31. data/docsite/source/extensions/maybe.html.md +57 -0
  32. data/docsite/source/extensions/monads.html.md +61 -0
  33. data/docsite/source/getting-started.html.md +57 -0
  34. data/docsite/source/hash-schemas.html.md +169 -0
  35. data/docsite/source/index.html.md +156 -0
  36. data/docsite/source/map.html.md +17 -0
  37. data/docsite/source/optional-values.html.md +35 -0
  38. data/docsite/source/sum.html.md +21 -0
  39. data/dry-types.gemspec +19 -19
  40. data/lib/dry/types.rb +9 -4
  41. data/lib/dry/types/any.rb +2 -2
  42. data/lib/dry/types/array.rb +6 -0
  43. data/lib/dry/types/array/constructor.rb +32 -0
  44. data/lib/dry/types/array/member.rb +10 -3
  45. data/lib/dry/types/builder.rb +2 -2
  46. data/lib/dry/types/builder_methods.rb +34 -8
  47. data/lib/dry/types/coercions.rb +19 -6
  48. data/lib/dry/types/coercions/params.rb +4 -4
  49. data/lib/dry/types/compiler.rb +2 -2
  50. data/lib/dry/types/constrained.rb +6 -1
  51. data/lib/dry/types/constructor.rb +10 -42
  52. data/lib/dry/types/constructor/function.rb +4 -5
  53. data/lib/dry/types/core.rb +27 -8
  54. data/lib/dry/types/decorator.rb +3 -2
  55. data/lib/dry/types/enum.rb +2 -1
  56. data/lib/dry/types/extensions.rb +4 -0
  57. data/lib/dry/types/extensions/maybe.rb +9 -1
  58. data/lib/dry/types/extensions/monads.rb +29 -0
  59. data/lib/dry/types/hash.rb +11 -12
  60. data/lib/dry/types/hash/constructor.rb +5 -5
  61. data/lib/dry/types/json.rb +4 -0
  62. data/lib/dry/types/lax.rb +4 -4
  63. data/lib/dry/types/map.rb +8 -4
  64. data/lib/dry/types/meta.rb +1 -1
  65. data/lib/dry/types/module.rb +6 -6
  66. data/lib/dry/types/nominal.rb +3 -4
  67. data/lib/dry/types/params.rb +9 -0
  68. data/lib/dry/types/predicate_inferrer.rb +197 -0
  69. data/lib/dry/types/predicate_registry.rb +34 -0
  70. data/lib/dry/types/primitive_inferrer.rb +97 -0
  71. data/lib/dry/types/printer.rb +17 -12
  72. data/lib/dry/types/schema.rb +16 -22
  73. data/lib/dry/types/schema/key.rb +19 -1
  74. data/lib/dry/types/spec/types.rb +6 -7
  75. data/lib/dry/types/sum.rb +2 -2
  76. data/lib/dry/types/version.rb +1 -1
  77. metadata +67 -35
  78. data/.travis.yml +0 -27
@@ -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
@@ -127,7 +127,7 @@ module Dry
127
127
  #
128
128
  # @api public
129
129
  def constructor(constructor = nil, **options, &block)
130
- constructor_type.new(with(options), fn: constructor || block)
130
+ constructor_type.new(with(**options), fn: constructor || block)
131
131
  end
132
132
  end
133
133
  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,7 +115,23 @@ 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)
119
+ end
120
+
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
109
135
  end
110
136
  end
111
137
  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
@@ -68,12 +68,12 @@ module Dry
68
68
 
69
69
  def visit_hash(node)
70
70
  opts, meta = node
71
- registry['nominal.hash'].with(opts.merge(meta: meta))
71
+ registry['nominal.hash'].with(**opts, meta: meta)
72
72
  end
73
73
 
74
74
  def visit_schema(node)
75
75
  keys, options, meta = node
76
- registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(options.merge(meta: meta))
76
+ registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(**options, meta: meta)
77
77
  end
78
78
 
79
79
  def visit_json_hash(node)
@@ -24,7 +24,7 @@ module Dry
24
24
  # @param [Hash] options
25
25
  #
26
26
  # @api public
27
- def initialize(type, options)
27
+ def initialize(type, **options)
28
28
  super
29
29
  @rule = options.fetch(:rule)
30
30
  end
@@ -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,36 +44,11 @@ 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
77
50
  def call_safe(input)
78
- coerced = fn.(input) { return yield }
51
+ coerced = fn.(input) { |output = input| return yield(output) }
79
52
  type.call_safe(coerced) { |output = coerced| yield(output) }
80
53
  end
81
54
 
@@ -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)
@@ -112,7 +85,7 @@ module Dry
112
85
  #
113
86
  # @api public
114
87
  def constructor(new_fn = nil, **options, &block)
115
- with({**options, fn: fn >> (new_fn || block)})
88
+ with(**options, fn: fn >> (new_fn || block))
116
89
  end
117
90
  alias_method :append, :constructor
118
91
  alias_method :>>, :constructor
@@ -141,7 +114,7 @@ module Dry
141
114
  #
142
115
  # @api public
143
116
  def prepend(new_fn = nil, **options, &block)
144
- with({**options, fn: fn << (new_fn || block)})
117
+ with(**options, fn: fn << (new_fn || block))
145
118
  end
146
119
  alias_method :<<, :prepend
147
120
 
@@ -150,7 +123,7 @@ module Dry
150
123
  # @return [Lax]
151
124
  # @api public
152
125
  def lax
153
- Lax.new(Constructor.new(type.lax, options))
126
+ Lax.new(Constructor.new(type.lax, **options))
154
127
  end
155
128
 
156
129
  # Wrap the type with a proc
@@ -182,10 +155,10 @@ 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)
188
- response.constructor_type.new(response, options)
160
+ if response.is_a?(Type) && type.class == response.class
161
+ response.constructor_type.new(response, **options)
189
162
  else
190
163
  response
191
164
  end
@@ -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)
@@ -14,7 +14,7 @@ module Dry
14
14
  attr_reader :type
15
15
 
16
16
  # @param [Type] type
17
- def initialize(type, *)
17
+ def initialize(type, *, **)
18
18
  super
19
19
  @type = type
20
20
  end
@@ -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)
@@ -101,6 +101,7 @@ module Dry
101
101
  super
102
102
  end
103
103
  end
104
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
104
105
 
105
106
  # Replace underlying type
106
107
  #