sorbet-coerce 0.1.6 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9749ed0f8347a42911400212d8a30279876e10b1983daabc16167f819fdd76bd
4
- data.tar.gz: 2866bbe6175228c14b563928bf44521608486ca7c1cb2aa6dc4bbf19aafd0bcf
3
+ metadata.gz: 8c5679d22c95a68cf624bef8a9d3f0c62b9b33f158b439b4115b2cb2f18dc508
4
+ data.tar.gz: f74881e16d72bb4b07b5de8072ef1f10d7794e8e6af249f9edcf132628a3a0e2
5
5
  SHA512:
6
- metadata.gz: d8f9f8e5086248a4db00ef26a0285fb9363817f552f77658d0ca8c0b5c373221df6c67116b9735fcd33d41831f6c49d139dfd1608e44adbe0cd5168335b863c5
7
- data.tar.gz: a159fd6c75c112e71ad4e801ae759b01051f1a1e0ef2d02f875e40b4444b25b098a9049f3ab7dea596b5373c5e86759ba3a78da622978ef8147ba4883a51c444
6
+ metadata.gz: 5f0a4df69764c02a35aba2101740beb6445bc77dec5f5edea12c31c7db18837696153e4f41ab5604ba374a610a203377ec38f621c2d93f5e58a57d72e6e05b4b
7
+ data.tar.gz: 334a49158e79661994d826dbd5845b071260d66f5ffeae49d45d22c6c62a2deeeccb11fe3efe8e616856c2eb3bf60c12031795c4cace5817b87e6ca7ebfa633f
@@ -0,0 +1,17 @@
1
+ # typed: true
2
+ module SafeType
3
+ class CoercionError < StandardError; end
4
+ end
5
+
6
+ module TypeCoerce
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Elem = type_member
11
+
12
+ sig { params(args: T.untyped, raise_coercion_error: T.nilable(T::Boolean)).returns(Elem) }
13
+ def from(args, raise_coercion_error: nil); end
14
+
15
+ class CoercionError < SafeType::CoercionError; end
16
+ class ShapeError < SafeType::CoercionError; end
17
+ end
@@ -0,0 +1,15 @@
1
+ # typed: true
2
+ require 'sorbet-runtime'
3
+
4
+ module TypeCoerce
5
+ module Configuration
6
+ class << self
7
+ extend T::Sig
8
+
9
+ sig { returns(T::Boolean) }
10
+ attr_accessor :raise_coercion_error
11
+ end
12
+ end
13
+ end
14
+
15
+ TypeCoerce::Configuration.raise_coercion_error = true
@@ -1,13 +1,14 @@
1
1
  # typed: strict
2
2
  require 'safe_type'
3
+ require 'set'
3
4
  require 'sorbet-runtime'
4
5
  require 'polyfill'
5
6
 
6
7
  using Polyfill(Hash: %w[#slice])
7
8
 
8
- module T; end
9
+ module TypeCoerce; end
9
10
 
10
- module T::Private
11
+ module TypeCoerce::Private
11
12
  class Converter
12
13
  extend T::Sig
13
14
 
@@ -22,12 +23,16 @@ module T::Private
22
23
  ], T.untyped)
23
24
 
24
25
  protected
25
- sig { params(value: T.untyped, type: T.untyped).returns(T.untyped) }
26
- def _convert(value, type)
27
- if type.is_a?(T::Types::TypedArray)
28
- _convert_to_a(value, type.type)
26
+ sig { params(value: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
27
+ def _convert(value, type, raise_coercion_error)
28
+ if type.is_a?(T::Types::Untyped)
29
+ value
30
+ elsif type.is_a?(T::Types::TypedArray)
31
+ _convert_to_a(value, type.type, raise_coercion_error)
32
+ elsif type.is_a?(T::Types::TypedSet)
33
+ Set.new(_convert_to_a(value, type.type, raise_coercion_error))
29
34
  elsif type.is_a?(T::Types::Simple)
30
- _convert(value, type.raw_type)
35
+ _convert(value, type.raw_type, raise_coercion_error)
31
36
  elsif type.is_a?(T::Types::Union)
32
37
  true_idx = T.let(nil, T.nilable(Integer))
33
38
  false_idx = T.let(nil, T.nilable(Integer))
@@ -49,23 +54,36 @@ module T::Private
49
54
  )
50
55
 
51
56
  if !true_idx.nil? && !false_idx.nil?
52
- _convert_simple(value, T::Boolean)
57
+ _convert_simple(value, T::Boolean, raise_coercion_error)
53
58
  else
54
- _convert(value, type.types[nil_idx == 0 ? 1 : 0])
59
+ _convert(value, type.types[nil_idx == 0 ? 1 : 0], raise_coercion_error)
55
60
  end
61
+ elsif type.is_a?(T::Types::TypedHash)
62
+ return {} if _nil_like?(value, type)
63
+
64
+ unless value.respond_to?(:map)
65
+ raise TypeCoerce::ShapeError.new(value, type)
66
+ end
67
+
68
+ value.map do |k, v|
69
+ [
70
+ _convert(k, type.keys, raise_coercion_error),
71
+ _convert(v, type.values, raise_coercion_error),
72
+ ]
73
+ end.to_h
56
74
  elsif Object.const_defined?('T::Private::Types::TypeAlias') &&
57
75
  type.is_a?(T::Private::Types::TypeAlias)
58
- _convert(value, type.aliased_type)
59
- elsif type < T::Struct
60
- args = _build_args(value, type.props)
76
+ _convert(value, type.aliased_type, raise_coercion_error)
77
+ elsif type.respond_to?(:<) && type < T::Struct
78
+ args = _build_args(value, type, raise_coercion_error)
61
79
  type.new(args)
62
80
  else
63
- _convert_simple(value, type)
81
+ _convert_simple(value, type, raise_coercion_error)
64
82
  end
65
83
  end
66
84
 
67
- sig { params(value: T.untyped, type: T.untyped).returns(T.untyped) }
68
- def _convert_simple(value, type)
85
+ sig { params(value: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
86
+ def _convert_simple(value, type, raise_coercion_error)
69
87
  return nil if _nil_like?(value, type)
70
88
 
71
89
  safe_type_rule = T.let(nil, T.untyped)
@@ -79,31 +97,46 @@ module T::Private
79
97
  else
80
98
  safe_type_rule = type
81
99
  end
82
- SafeType::coerce(value, safe_type_rule)
100
+
101
+ if safe_type_rule.is_a?(SafeType::Rule)
102
+ SafeType::coerce(value, safe_type_rule)
103
+ else
104
+ type.new(value)
105
+ end
83
106
  rescue SafeType::EmptyValueError, SafeType::CoercionError
84
- value
85
- rescue SafeType::InvalidRuleError
86
- type.new(value)
107
+ if raise_coercion_error
108
+ raise TypeCoerce::CoercionError.new(value, type)
109
+ else
110
+ nil
111
+ end
87
112
  end
88
113
 
89
- sig { params(ary: T.untyped, type: T.untyped).returns(T.untyped) }
90
- def _convert_to_a(ary, type)
114
+ sig { params(ary: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
115
+ def _convert_to_a(ary, type, raise_coercion_error)
91
116
  return [] if _nil_like?(ary, type)
92
117
 
93
- # Checked by the T.let at root
94
- ary.respond_to?(:map) ? ary.map { |value| _convert(value, type) } : ary
118
+ unless ary.respond_to?(:map)
119
+ raise TypeCoerce::ShapeError.new(ary, type)
120
+ end
121
+
122
+ ary.map { |value| _convert(value, type, raise_coercion_error) }
95
123
  end
96
124
 
97
- sig { params(args: T.untyped, props: T.untyped).returns(T.untyped) }
98
- def _build_args(args, props)
125
+ sig { params(args: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
126
+ def _build_args(args, type, raise_coercion_error)
99
127
  return {} if _nil_like?(args, Hash)
100
128
 
129
+ unless args.respond_to?(:each_pair)
130
+ raise TypeCoerce::ShapeError.new(args, type)
131
+ end
132
+
133
+ props = type.props
101
134
  args.map { |name, value|
102
135
  key = name.to_sym
103
136
  [
104
137
  key,
105
138
  (!props.include?(key) || value.nil?) ?
106
- nil : _convert(value, props[key][:type]),
139
+ nil : _convert(value, props[key][:type], raise_coercion_error),
107
140
  ]
108
141
  }.to_h.slice(*props.keys)
109
142
  end
@@ -0,0 +1,10 @@
1
+ # typed: true
2
+ module T
3
+ module Private
4
+ module Types
5
+ class TypeAlias
6
+ def aliased_type; end
7
+ end
8
+ end
9
+ end
10
+ end
data/lib/sorbet-coerce.rb CHANGED
@@ -1,22 +1,22 @@
1
1
  # typed: strict
2
+ require_relative 'configuration'
2
3
  require 'private/converter'
3
4
  require 'safe_type'
4
5
 
5
- module T
6
+ module TypeCoerce
6
7
  class CoercionError < SafeType::CoercionError; end
8
+ class ShapeError < SafeType::CoercionError; end
7
9
 
8
- module Coerce
9
- define_singleton_method(:[]) do |type|
10
- Class.new(T::Private::Converter) do
11
- define_method(:to_s) { "#{name}#[#{type.to_s}]" }
10
+ define_singleton_method(:[]) do |type|
11
+ Class.new(TypeCoerce::Private::Converter) do
12
+ define_method(:to_s) { "#{name}#[#{type.to_s}]" }
12
13
 
13
- define_method(:from) do |args|
14
- begin
15
- T.send('let', send('_convert', args, type), type)
16
- rescue TypeError
17
- raise CoercionError.new(args, type)
18
- end
14
+ define_method(:from) do |args, raise_coercion_error: nil|
15
+ if raise_coercion_error.nil?
16
+ raise_coercion_error = TypeCoerce::Configuration.raise_coercion_error
19
17
  end
18
+
19
+ T.send('let', send('_convert', args, type, raise_coercion_error), type)
20
20
  end
21
21
  end
22
22
  end
data/spec/coerce_spec.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  require 'sorbet-coerce'
3
3
  require 'sorbet-runtime'
4
4
 
5
- describe T::Coerce do
5
+ describe TypeCoerce do
6
6
  context 'when given T::Struct' do
7
7
  class ParamInfo < T::Struct
8
8
  const :name, String
@@ -27,6 +27,14 @@ describe T::Coerce do
27
27
  const :a, Integer, default: 1
28
28
  end
29
29
 
30
+ class HashParams < T::Struct
31
+ const :myhash, T::Hash[String, Integer]
32
+ end
33
+
34
+ class HashParamsWithDefault < T::Struct
35
+ const :myhash, T::Hash[String, Integer], default: Hash['a' => 1]
36
+ end
37
+
30
38
  class CustomType
31
39
  attr_reader :a
32
40
 
@@ -44,7 +52,7 @@ describe T::Coerce do
44
52
  end
45
53
 
46
54
  let!(:param) {
47
- T::Coerce[Param].new.from({
55
+ TypeCoerce[Param].new.from({
48
56
  id: 1,
49
57
  info: {
50
58
  name: 'mango',
@@ -60,7 +68,7 @@ describe T::Coerce do
60
68
  }
61
69
 
62
70
  let!(:param2) {
63
- T::Coerce[Param].new.from({
71
+ TypeCoerce[Param].new.from({
64
72
  id: '2',
65
73
  info: {
66
74
  name: 'honeydew',
@@ -101,7 +109,7 @@ describe T::Coerce do
101
109
  expect(param2.opt.notes).to eql []
102
110
 
103
111
  expect {
104
- T::Coerce[Param].new.from({
112
+ TypeCoerce[Param].new.from({
105
113
  id: 3,
106
114
  info: {
107
115
  # missing required name
@@ -110,8 +118,8 @@ describe T::Coerce do
110
118
  })
111
119
  }.to raise_error(ArgumentError)
112
120
 
113
- expect(T::Coerce[DefaultParams].new.from(nil).a).to be 1
114
- expect(T::Coerce[DefaultParams].new.from('').a).to be 1
121
+ expect(TypeCoerce[DefaultParams].new.from(nil).a).to be 1
122
+ expect(TypeCoerce[DefaultParams].new.from('').a).to be 1
115
123
  end
116
124
  end
117
125
 
@@ -123,63 +131,115 @@ describe T::Coerce do
123
131
 
124
132
  it 'raises an error' do
125
133
  expect {
126
- T::Coerce[Param2].new.from({id: 1, info: 1})
134
+ TypeCoerce[Param2].new.from({id: 1, info: 1})
127
135
  }.to raise_error(ArgumentError)
128
136
  end
129
137
  end
130
138
 
131
139
  context 'when given primitive types' do
132
140
  it 'reveals the right type' do
133
- T.assert_type!(T::Coerce[Integer].new.from(1), Integer)
134
- T.assert_type!(T::Coerce[Integer].new.from('1.0'), Integer)
135
- T.assert_type!(T::Coerce[T.nilable(Integer)].new.from(nil), T.nilable(Integer))
141
+ T.assert_type!(TypeCoerce[Integer].new.from(1), Integer)
142
+ T.assert_type!(TypeCoerce[Integer].new.from('1.0'), Integer)
143
+ T.assert_type!(TypeCoerce[T.nilable(Integer)].new.from(nil), T.nilable(Integer))
136
144
  end
137
145
 
138
146
  it 'coreces correctly' do
139
- expect{T::Coerce[Integer].new.from(nil)}.to raise_error(T::CoercionError)
140
- expect(T::Coerce[T.nilable(Integer)].new.from(nil) || 1).to eql 1
141
- expect(T::Coerce[Integer].new.from(2)).to eql 2
142
- expect(T::Coerce[Integer].new.from('1.0')).to eql 1
147
+ expect{TypeCoerce[Integer].new.from(nil)}.to raise_error(TypeError)
148
+ expect(TypeCoerce[T.nilable(Integer)].new.from(nil) || 1).to eql 1
149
+ expect(TypeCoerce[Integer].new.from(2)).to eql 2
150
+ expect(TypeCoerce[Integer].new.from('1.0')).to eql 1
143
151
 
144
- expect{T::Coerce[T.nilable(Integer)].new.from('invalid integer string')}.to raise_error(T::CoercionError)
145
- expect(T::Coerce[Float].new.from('1.0')).to eql 1.0
152
+ expect{TypeCoerce[T.nilable(Integer)].new.from('invalid integer string')}.to raise_error(TypeCoerce::CoercionError)
153
+ expect(TypeCoerce[Float].new.from('1.0')).to eql 1.0
146
154
 
147
- expect(T::Coerce[T::Boolean].new.from('false')).to be false
148
- expect(T::Coerce[T::Boolean].new.from('true')).to be true
155
+ expect(TypeCoerce[T::Boolean].new.from('false')).to be false
156
+ expect(TypeCoerce[T::Boolean].new.from('true')).to be true
149
157
 
150
- expect(T::Coerce[T.nilable(Integer)].new.from('')).to be nil
151
- expect{T::Coerce[T.nilable(Integer)].new.from([])}.to raise_error(T::CoercionError)
152
- expect(T::Coerce[T.nilable(String)].new.from('')).to eql ''
158
+ expect(TypeCoerce[T.nilable(Integer)].new.from('')).to be nil
159
+ expect{TypeCoerce[T.nilable(Integer)].new.from([])}.to raise_error(TypeCoerce::CoercionError)
160
+ expect(TypeCoerce[T.nilable(String)].new.from('')).to eql ''
153
161
  end
154
162
  end
155
163
 
156
164
  context 'when given custom types' do
157
165
  it 'coerces correctly' do
158
- T.assert_type!(T::Coerce[CustomType].new.from(a: 1), CustomType)
159
- expect(T::Coerce[CustomType].new.from(1).a).to be 1
166
+ T.assert_type!(TypeCoerce[CustomType].new.from(a: 1), CustomType)
167
+ expect(TypeCoerce[CustomType].new.from(1).a).to be 1
160
168
 
161
- expect{T::Coerce[UnsupportedCustomType].new.from(1)}.to raise_error(ArgumentError)
162
- expect{T::Coerce[CustomType2].new.from(1)}.to raise_error(T::CoercionError)
169
+ expect{TypeCoerce[UnsupportedCustomType].new.from(1)}.to raise_error(ArgumentError)
170
+ # CustomType2.new(anything) returns Integer 1; 1.is_a?(CustomType2) == false
171
+ expect{TypeCoerce[CustomType2].new.from(1)}.to raise_error(TypeError)
163
172
  end
164
173
  end
165
174
 
166
175
  context 'when dealing with arries' do
167
176
  it 'coreces correctly' do
168
- expect(T::Coerce[T::Array[Integer]].new.from(nil)).to eql []
169
- expect(T::Coerce[T::Array[Integer]].new.from('')).to eql []
170
- expect{T::Coerce[T::Array[Integer]].new.from('not an array')}.to raise_error(T::CoercionError)
171
- expect{T::Coerce[T::Array[Integer]].new.from('1')}.to raise_error(T::CoercionError)
172
- expect(T::Coerce[T::Array[Integer]].new.from(['1', '2', '3'])).to eql [1, 2, 3]
173
- expect{T::Coerce[T::Array[Integer]].new.from(['1', 'invalid', '3'])}.to raise_error(T::CoercionError)
174
- expect{T::Coerce[T::Array[Integer]].new.from({a: 1})}.to raise_error(T::CoercionError)
175
-
176
- infos = T::Coerce[T::Array[ParamInfo]].new.from([{name: 'a', skill_ids: []}])
177
+ expect(TypeCoerce[T::Array[Integer]].new.from(nil)).to eql []
178
+ expect(TypeCoerce[T::Array[Integer]].new.from('')).to eql []
179
+ expect{TypeCoerce[T::Array[Integer]].new.from('not an array')}.to raise_error(TypeCoerce::ShapeError)
180
+ expect{TypeCoerce[T::Array[Integer]].new.from('1')}.to raise_error(TypeCoerce::ShapeError)
181
+ expect(TypeCoerce[T::Array[Integer]].new.from(['1', '2', '3'])).to eql [1, 2, 3]
182
+ expect{TypeCoerce[T::Array[Integer]].new.from(['1', 'invalid', '3'])}.to raise_error(TypeCoerce::CoercionError)
183
+ expect{TypeCoerce[T::Array[Integer]].new.from({a: 1})}.to raise_error(TypeCoerce::CoercionError)
184
+
185
+ infos = TypeCoerce[T::Array[ParamInfo]].new.from([{name: 'a', skill_ids: []}])
177
186
  T.assert_type!(infos, T::Array[ParamInfo])
178
187
  expect(infos.first.name).to eql 'a'
179
188
 
180
- infos = T::Coerce[T::Array[ParamInfo]].new.from([{name: 'b', skill_ids: []}])
189
+ infos = TypeCoerce[T::Array[ParamInfo]].new.from([{name: 'b', skill_ids: []}])
181
190
  T.assert_type!(infos, T::Array[ParamInfo])
182
191
  expect(infos.first.name).to eql 'b'
192
+
193
+ expect {
194
+ TypeCoerce[ParamInfo2].new.from({a: nil, b: nil})
195
+ }.to raise_error(TypeError)
196
+ end
197
+ end
198
+
199
+ context 'when dealing with hashes' do
200
+ it 'coreces correctly' do
201
+ expect(TypeCoerce[T::Hash[T.untyped, T.untyped]].new.from(nil)).to eql({})
202
+
203
+ expect(TypeCoerce[T::Hash[String, T::Boolean]].new.from({
204
+ a: 'true',
205
+ b: 'false',
206
+ })).to eql({
207
+ 'a' => true,
208
+ 'b' => false,
209
+ })
210
+
211
+ expect(TypeCoerce[HashParams].new.from({
212
+ myhash: {'a' => '1', 'b' => '2'},
213
+ }).myhash).to eql({'a' => 1, 'b' => 2})
214
+
215
+ expect(TypeCoerce[HashParamsWithDefault].new.from({}).myhash).to eql({'a' => 1})
216
+
217
+ expect {
218
+ TypeCoerce[T::Hash[String, T::Boolean]].new.from({
219
+ a: 'invalid',
220
+ b: 'false',
221
+ })
222
+ }.to raise_error(TypeCoerce::CoercionError)
223
+
224
+ expect {
225
+ TypeCoerce[T::Hash[String, Integer]].new.from(1)
226
+ }.to raise_error(TypeCoerce::ShapeError)
227
+ end
228
+ end
229
+
230
+ context 'when dealing with sets' do
231
+ it 'coreces correctly' do
232
+ expect(TypeCoerce[T::Set[Integer]].new.from(
233
+ Set.new(['1', '2', '3'])
234
+ )).to eq Set.new([1, 2, 3])
235
+
236
+ expect {
237
+ TypeCoerce[T::Set[Integer]].new.from(Set.new(['1', 'invalid', '3']))
238
+ }.to raise_error(TypeCoerce::CoercionError)
239
+
240
+ expect {
241
+ TypeCoerce[T::Set[Integer]].new.from(1)
242
+ }.to raise_error(TypeCoerce::ShapeError)
183
243
  end
184
244
  end
185
245
 
@@ -187,7 +247,14 @@ describe T::Coerce do
187
247
  MyType = T.type_alias(T::Boolean)
188
248
 
189
249
  it 'coerces correctly' do
190
- expect(T::Coerce[MyType].new.from('false')).to be false
250
+ expect(TypeCoerce[MyType].new.from('false')).to be false
191
251
  end
192
252
  end
253
+
254
+ it 'works with T.untyped' do
255
+ expect(TypeCoerce[T.untyped].new.from(1)).to eql 1
256
+
257
+ obj = CustomType.new(1)
258
+ expect(TypeCoerce[T::Hash[String, T.untyped]].new.from({a: obj})).to eq({'a' => obj})
259
+ end
193
260
  end
data/spec/nested_spec.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  require 'sorbet-coerce'
3
3
  require 'sorbet-runtime'
4
4
 
5
- describe T::Coerce do
5
+ describe TypeCoerce do
6
6
  context 'when given nested types' do
7
7
  class User < T::Struct
8
8
  const :id, Integer
@@ -15,7 +15,7 @@ describe T::Coerce do
15
15
  end
16
16
 
17
17
  it 'works with nest T::Struct' do
18
- converted = T::Coerce[NestedParam].new.from({
18
+ converted = TypeCoerce[NestedParam].new.from({
19
19
  users: [{id: '1'}],
20
20
  params: {
21
21
  users: [{id: '2', valid: 'true'}],
@@ -42,16 +42,16 @@ describe T::Coerce do
42
42
 
43
43
  it 'works with nest T::Array' do
44
44
  expect {
45
- T::Coerce[T::Array[T.nilable(Integer)]].new.from(['1', 'invalid', '3'])
46
- }.to raise_error(T::CoercionError)
45
+ TypeCoerce[T::Array[T.nilable(Integer)]].new.from(['1', 'invalid', '3'])
46
+ }.to raise_error(TypeCoerce::CoercionError)
47
47
  expect(
48
- T::Coerce[T::Array[T::Array[Integer]]].new.from([nil])
48
+ TypeCoerce[T::Array[T::Array[Integer]]].new.from([nil])
49
49
  ).to eql([[]])
50
50
  expect(
51
- T::Coerce[T::Array[T::Array[Integer]]].new.from([['1'], ['2'], ['3']]),
51
+ TypeCoerce[T::Array[T::Array[Integer]]].new.from([['1'], ['2'], ['3']]),
52
52
  ).to eql [[1], [2], [3]]
53
53
 
54
- expect(T::Coerce[
54
+ expect(TypeCoerce[
55
55
  T::Array[
56
56
  T::Array[
57
57
  T::Array[User]
@@ -59,7 +59,7 @@ describe T::Coerce do
59
59
  ]
60
60
  ].new.from([[[{id: '1'}]]]).flatten.first.id).to eql(1)
61
61
 
62
- expect(T::Coerce[
62
+ expect(TypeCoerce[
63
63
  T::Array[
64
64
  T::Array[
65
65
  T::Array[
@@ -71,9 +71,18 @@ describe T::Coerce do
71
71
  ]
72
72
  ].new.from([[[[[{id: 1}]]]]]).flatten.first.id).to eql 1
73
73
 
74
- expect(T::Coerce[
74
+ expect(TypeCoerce[
75
75
  T.nilable(T::Array[T.nilable(T::Array[T.nilable(User)])])
76
76
  ].new.from([[{id: '1'}]]).flatten.map(&:id)).to eql([1])
77
77
  end
78
+
79
+ it 'works with nested T::Hash' do
80
+ expect(
81
+ TypeCoerce[T::Hash[Symbol, T::Hash[Symbol, Integer]]].new.from({
82
+ a: nil,
83
+ b: {c: '1'}
84
+ })
85
+ ).to eql({a: {}, b: {c: 1}})
86
+ end
78
87
  end
79
88
  end
@@ -2,11 +2,15 @@
2
2
  require 'sorbet-coerce'
3
3
  require 'sorbet-runtime'
4
4
 
5
- describe T::Coerce do
5
+ describe TypeCoerce do
6
6
  context 'when type errors are soft errors' do
7
7
  let(:ignore_error) { Proc.new {} }
8
8
 
9
9
  before(:each) do
10
+ allow(TypeCoerce::Configuration).to receive(
11
+ :raise_coercion_error,
12
+ ).and_return(false)
13
+
10
14
  allow(T::Configuration).to receive(
11
15
  :inline_type_error_handler,
12
16
  ).and_return(ignore_error)
@@ -34,20 +38,28 @@ describe T::Coerce do
34
38
  def self.new(a); 1; end
35
39
  end
36
40
 
41
+ let(:invalid_arg) { 'invalid integer string' }
42
+
43
+ it 'overwrites the global config when inline config is set' do
44
+ expect {
45
+ TypeCoerce[Integer].new.from(invalid_arg, raise_coercion_error: true)
46
+ }.to raise_error(TypeCoerce::CoercionError)
47
+ end
48
+
37
49
  it 'works as expected' do
38
- invalid_arg = 'invalid integer string'
39
- expect(T::Coerce[Integer].new.from(invalid_arg)).to eql(invalid_arg)
40
- expect(T::Coerce[T::Array[Integer]].new.from(1)).to be 1
41
- expect(T::Coerce[T::Array[Integer]].new.from(invalid_arg)).to eql(invalid_arg)
42
- expect(T::Coerce[T::Array[Integer]].new.from({a: 1})).to eql([[:a, 1]])
50
+ expect(TypeCoerce[Integer].new.from(invalid_arg)).to eql(nil)
51
+
52
+ expect{TypeCoerce[T::Array[Integer]].new.from(1)}.to raise_error(TypeCoerce::ShapeError)
53
+ expect(TypeCoerce[T::Array[Integer]].new.from({a: 1})).to eql([nil])
43
54
 
44
55
  expect {
45
- T::Coerce[CustomTypeRaisesHardError].new.from(1)
56
+ TypeCoerce[CustomTypeRaisesHardError].new.from(1)
46
57
  }.to raise_error(StandardError)
47
- expect(T::Coerce[CustomTypeDoesNotRiaseHardError].new.from(1)).to eql(1)
58
+ expect(TypeCoerce[CustomTypeDoesNotRiaseHardError].new.from(1)).to eql(1)
48
59
 
49
- if Gem.loaded_specs['sorbet-runtime'].version >= Gem::Version.new('0.4.4948')
50
- expect(T::Coerce[ParamsWithSortError].new.from({a: invalid_arg}).a).to eql(invalid_arg)
60
+ sorbet_version = Gem.loaded_specs['sorbet-runtime'].version
61
+ if sorbet_version >= Gem::Version.new('0.4.4948')
62
+ expect(TypeCoerce[ParamsWithSortError].new.from({a: invalid_arg}).a).to eql(nil)
51
63
  end
52
64
  end
53
65
  end
@@ -0,0 +1,22 @@
1
+ # typed: true
2
+ require 'sorbet-coerce'
3
+
4
+ T.assert_type!(TypeCoerce[Integer].new.from('1'), Integer)
5
+ T.assert_type!(
6
+ TypeCoerce[T.nilable(Integer)].new.from('invalid', raise_coercion_error: false),
7
+ T.nilable(Integer),
8
+ )
9
+
10
+ TypeCoerce::Configuration.raise_coercion_error = true
11
+ coercion_error = nil
12
+ begin
13
+ TypeCoerce[T.nilable(Integer)].new.from('invalid')
14
+ rescue TypeCoerce::CoercionError => e
15
+ coercion_error = e
16
+ end
17
+ raise 'no coercion error is raised' unless coercion_error
18
+
19
+ T.assert_type!(
20
+ TypeCoerce[T.nilable(Integer)].new.from('invalid', raise_coercion_error: false),
21
+ T.nilable(Integer),
22
+ )
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorbet-coerce
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chan Zuckerberg Initiative
@@ -14,14 +14,14 @@ dependencies:
14
14
  name: sorbet
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 0.4.4704
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.4.4704
27
27
  - !ruby/object:Gem::Dependency
@@ -62,14 +62,14 @@ dependencies:
62
62
  name: sorbet-runtime
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - "~>"
65
+ - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: 0.4.4704
68
68
  type: :runtime
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - "~>"
72
+ - - ">="
73
73
  - !ruby/object:Gem::Version
74
74
  version: 0.4.4704
75
75
  - !ruby/object:Gem::Dependency
@@ -118,12 +118,15 @@ executables: []
118
118
  extensions: []
119
119
  extra_rdoc_files: []
120
120
  files:
121
+ - lib/bundled_rbi/sorbet-coerce.rbi
122
+ - lib/configuration.rb
121
123
  - lib/private/converter.rb
124
+ - lib/private/coverter.rbi
122
125
  - lib/sorbet-coerce.rb
123
- - rbi/sorbet-coerce.rbi
124
126
  - spec/coerce_spec.rb
125
127
  - spec/nested_spec.rb
126
128
  - spec/soft_error_spec.rb
129
+ - spec/sorbet_test_cases.rb
127
130
  - spec/spec_helper.rb
128
131
  homepage: https://github.com/chanzuckerberg/sorbet-coerce
129
132
  licenses:
@@ -137,7 +140,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
140
  requirements:
138
141
  - - ">="
139
142
  - !ruby/object:Gem::Version
140
- version: 2.3.0
143
+ version: 2.4.0
141
144
  required_rubygems_version: !ruby/object:Gem::Requirement
142
145
  requirements:
143
146
  - - ">="
@@ -1,20 +0,0 @@
1
- # typed: true
2
- module T
3
- module Coerce
4
- extend T::Sig
5
- extend T::Generic
6
-
7
- Elem = type_member
8
-
9
- sig { params(args: T.untyped).returns(Elem) }
10
- def from(args); end
11
- end
12
-
13
- module Private
14
- module Types
15
- class TypeAlias
16
- def aliased_type; end
17
- end
18
- end
19
- end
20
- end