sorbet-coerce 0.1.5 → 0.2.3

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: 5e0850189628e40cec7b240cd1a12c5abf995369f363444b7f5ecd398239bfa4
4
- data.tar.gz: 06756e0942061e2feb84958027a666a86b52345000b797a5cab3ceaccbfce0f9
3
+ metadata.gz: f267197b77c7b2c9ed7558ad7f94818fc54f9af5fc56c99c9d824af47e40bf78
4
+ data.tar.gz: 04da4e2a3ebdde38cf4d2e6d60200c365d371250f9d33f588f67adc5b766c6b2
5
5
  SHA512:
6
- metadata.gz: b08186d2c9c95bc3933f15895e23031a09fd49af3d16886c82433831e5d395c4a496c9e58a2b22c42630d01b925d151beca01656446a3d10b0ca46acab83dcad
7
- data.tar.gz: 9dcec07cc34048713d4ea627b1afac67c6885e82e8623ecad4b2f36af763c70c13e7d79a6d295c36cb2d502f62837b7df5f304c29d845f40d5be2b75660b64b7
6
+ metadata.gz: 6ee1d39f9e9b12ebdb2d3f1f9c4066ef2f7707329dfe10ab3330fc33b9e943ae9560028ec467203efad12bad567eb5b226ae4669be0b378bf1897a3ca84fbe37
7
+ data.tar.gz: a989b4a83745234e88e5c1ba5f26bda2d57be9ec0f61d26d4ece75f5db68bc88130fad68ec9fd43c6a4a44ae263231b65c8a0235bd34188b9c875f377ad9ddee
@@ -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,28 +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
+ unless value.respond_to?(:map)
63
+ raise TypeCoerce::ShapeError.new(value, type)
64
+ end
65
+
66
+ value.map do |k, v|
67
+ [
68
+ _convert(k, type.keys, raise_coercion_error),
69
+ _convert(v, type.values, raise_coercion_error),
70
+ ]
71
+ end.to_h
56
72
  elsif Object.const_defined?('T::Private::Types::TypeAlias') &&
57
73
  type.is_a?(T::Private::Types::TypeAlias)
58
- _convert(value, type.aliased_type)
74
+ _convert(value, type.aliased_type, raise_coercion_error)
59
75
  elsif type < T::Struct
60
- args = _build_args(value, type.props)
61
- begin
62
- type.new(args)
63
- rescue
64
- nil
65
- end
76
+ args = _build_args(value, type, raise_coercion_error)
77
+ type.new(args)
66
78
  else
67
- _convert_simple(value, type)
79
+ _convert_simple(value, type, raise_coercion_error)
68
80
  end
69
81
  end
70
82
 
71
- sig { params(value: T.untyped, type: T.untyped).returns(T.untyped) }
72
- def _convert_simple(value, type)
73
- return nil if value.nil?
83
+ sig { params(value: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
84
+ def _convert_simple(value, type, raise_coercion_error)
85
+ return nil if _nil_like?(value, type)
86
+
74
87
  safe_type_rule = T.let(nil, T.untyped)
75
88
 
76
89
  if type == T::Boolean
@@ -84,38 +97,48 @@ module T::Private
84
97
  end
85
98
  SafeType::coerce(value, safe_type_rule)
86
99
  rescue SafeType::EmptyValueError, SafeType::CoercionError
87
- nil
88
- rescue SafeType::InvalidRuleError
89
- begin
90
- type.new(value)
91
- rescue
100
+ if raise_coercion_error
101
+ raise TypeCoerce::CoercionError.new(value, type)
102
+ else
92
103
  nil
93
104
  end
105
+ rescue SafeType::InvalidRuleError
106
+ type.new(value)
94
107
  end
95
108
 
96
- sig { params(ary: T.untyped, type: T.untyped).returns(T.untyped) }
97
- def _convert_to_a(ary, type)
98
- ary = [ary] unless ary.is_a?(::Array)
99
- T.send(
100
- 'let',
101
- ary.map { |value| _convert(value, type) },
102
- T.const_get('Array')[type],
103
- )
104
- rescue TypeError
105
- []
109
+ sig { params(ary: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
110
+ def _convert_to_a(ary, type, raise_coercion_error)
111
+ return [] if _nil_like?(ary, type)
112
+
113
+ unless ary.respond_to?(:map)
114
+ raise TypeCoerce::ShapeError.new(ary, type)
115
+ end
116
+
117
+ ary.map { |value| _convert(value, type, raise_coercion_error) }
106
118
  end
107
119
 
108
- sig { params(args: T.untyped, props: T.untyped).returns(T.untyped) }
109
- def _build_args(args, props)
110
- return {} if args.nil?
120
+ sig { params(args: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
121
+ def _build_args(args, type, raise_coercion_error)
122
+ return {} if _nil_like?(args, Hash)
123
+
124
+ unless args.respond_to?(:each_pair)
125
+ raise TypeCoerce::ShapeError.new(args, type)
126
+ end
127
+
128
+ props = type.props
111
129
  args.map { |name, value|
112
130
  key = name.to_sym
113
131
  [
114
132
  key,
115
133
  (!props.include?(key) || value.nil?) ?
116
- nil : _convert(value, props[key][:type]),
134
+ nil : _convert(value, props[key][:type], raise_coercion_error),
117
135
  ]
118
136
  }.to_h.slice(*props.keys)
119
137
  end
138
+
139
+ sig { params(value: T.untyped, type: T.untyped).returns(T::Boolean) }
140
+ def _nil_like?(value, type)
141
+ value.nil? || (value == '' && type != String)
142
+ end
120
143
  end
121
144
  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
@@ -1,15 +1,22 @@
1
1
  # typed: true
2
- module T
3
- module Coerce
4
- extend T::Sig
5
- extend T::Generic
2
+ module SafeType
3
+ class CoercionError < StandardError; end
4
+ end
6
5
 
7
- Elem = type_member
6
+ module TypeCoerce
7
+ extend T::Sig
8
+ extend T::Generic
8
9
 
9
- sig { params(args: T.untyped).returns(Elem) }
10
- def from(args); end
11
- end
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
12
14
 
15
+ class CoercionError < SafeType::CoercionError; end
16
+ class ShapeError < SafeType::CoercionError; end
17
+ end
18
+
19
+ module T
13
20
  module Private
14
21
  module Types
15
22
  class TypeAlias
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
@@ -13,7 +13,7 @@ describe T::Coerce do
13
13
  class ParamInfo2 < T::Struct
14
14
  const :a, Integer
15
15
  const :b, Integer
16
- const :notes, T::Array[String]
16
+ const :notes, T::Array[String], default: []
17
17
  end
18
18
 
19
19
  class Param < T::Struct
@@ -23,6 +23,14 @@ describe T::Coerce do
23
23
  const :opt, T.nilable(ParamInfo2)
24
24
  end
25
25
 
26
+ class DefaultParams < T::Struct
27
+ const :a, Integer, default: 1
28
+ end
29
+
30
+ class HashParams < T::Struct
31
+ const :myhash, T::Hash[String, Integer]
32
+ end
33
+
26
34
  class CustomType
27
35
  attr_reader :a
28
36
 
@@ -31,12 +39,16 @@ describe T::Coerce do
31
39
  end
32
40
  end
33
41
 
42
+ class CustomType2
43
+ def self.new(a); 1; end
44
+ end
45
+
34
46
  class UnsupportedCustomType
35
47
  # Does not respond to new
36
48
  end
37
49
 
38
50
  let!(:param) {
39
- T::Coerce[Param].new.from({
51
+ TypeCoerce[Param].new.from({
40
52
  id: 1,
41
53
  info: {
42
54
  name: 'mango',
@@ -52,7 +64,7 @@ describe T::Coerce do
52
64
  }
53
65
 
54
66
  let!(:param2) {
55
- T::Coerce[Param].new.from({
67
+ TypeCoerce[Param].new.from({
56
68
  id: '2',
57
69
  info: {
58
70
  name: 'honeydew',
@@ -82,7 +94,7 @@ describe T::Coerce do
82
94
  expect(param.info.lvl).to eql 100
83
95
  expect(param.info.name).to eql 'mango'
84
96
  expect(param.info.skill_ids).to eql [123, 456]
85
- expect(param.opt).to be nil # missing notes
97
+ expect(param.opt.notes).to eql []
86
98
 
87
99
  expect(param2.id).to eql 2
88
100
  expect(param2.info.name).to eql 'honeydew'
@@ -92,15 +104,18 @@ describe T::Coerce do
92
104
  expect(param2.opt.b).to eql 2
93
105
  expect(param2.opt.notes).to eql []
94
106
 
95
- expect(
96
- T::Coerce[T.nilable(Param)].new.from({
107
+ expect {
108
+ TypeCoerce[Param].new.from({
97
109
  id: 3,
98
110
  info: {
99
111
  # missing required name
100
112
  lvl: 2,
101
113
  },
102
114
  })
103
- ).to be nil
115
+ }.to raise_error(ArgumentError)
116
+
117
+ expect(TypeCoerce[DefaultParams].new.from(nil).a).to be 1
118
+ expect(TypeCoerce[DefaultParams].new.from('').a).to be 1
104
119
  end
105
120
  end
106
121
 
@@ -112,56 +127,112 @@ describe T::Coerce do
112
127
 
113
128
  it 'raises an error' do
114
129
  expect {
115
- T::Coerce[Param2].new.from({id: 1, info: 1})
130
+ TypeCoerce[Param2].new.from({id: 1, info: 1})
116
131
  }.to raise_error(ArgumentError)
117
132
  end
118
133
  end
119
134
 
120
135
  context 'when given primitive types' do
121
136
  it 'reveals the right type' do
122
- T.assert_type!(T::Coerce[Integer].new.from(1), Integer)
123
- T.assert_type!(T::Coerce[Integer].new.from('1.0'), Integer)
124
- T.assert_type!(T::Coerce[T.nilable(Integer)].new.from(nil), T.nilable(Integer))
137
+ T.assert_type!(TypeCoerce[Integer].new.from(1), Integer)
138
+ T.assert_type!(TypeCoerce[Integer].new.from('1.0'), Integer)
139
+ T.assert_type!(TypeCoerce[T.nilable(Integer)].new.from(nil), T.nilable(Integer))
125
140
  end
126
141
 
127
142
  it 'coreces correctly' do
128
- expect{T::Coerce[Integer].new.from(nil)}.to raise_error(T::CoercionError)
129
- expect(T::Coerce[T.nilable(Integer)].new.from(nil) || 1).to eql 1
130
- expect(T::Coerce[Integer].new.from(2)).to eql 2
131
- expect(T::Coerce[Integer].new.from('1.0')).to eql 1
143
+ expect{TypeCoerce[Integer].new.from(nil)}.to raise_error(TypeError)
144
+ expect(TypeCoerce[T.nilable(Integer)].new.from(nil) || 1).to eql 1
145
+ expect(TypeCoerce[Integer].new.from(2)).to eql 2
146
+ expect(TypeCoerce[Integer].new.from('1.0')).to eql 1
132
147
 
133
- expect(T::Coerce[T.nilable(Integer)].new.from('invalid integer string')).to be nil
134
- expect(T::Coerce[Float].new.from('1.0')).to eql 1.0
148
+ expect{TypeCoerce[T.nilable(Integer)].new.from('invalid integer string')}.to raise_error(TypeCoerce::CoercionError)
149
+ expect(TypeCoerce[Float].new.from('1.0')).to eql 1.0
135
150
 
136
- expect(T::Coerce[T::Boolean].new.from('false')).to be false
137
- expect(T::Coerce[T::Boolean].new.from('true')).to be true
151
+ expect(TypeCoerce[T::Boolean].new.from('false')).to be false
152
+ expect(TypeCoerce[T::Boolean].new.from('true')).to be true
153
+
154
+ expect(TypeCoerce[T.nilable(Integer)].new.from('')).to be nil
155
+ expect{TypeCoerce[T.nilable(Integer)].new.from([])}.to raise_error(TypeCoerce::CoercionError)
156
+ expect(TypeCoerce[T.nilable(String)].new.from('')).to eql ''
138
157
  end
139
158
  end
140
159
 
141
160
  context 'when given custom types' do
142
161
  it 'coerces correctly' do
143
- T.assert_type!(T::Coerce[CustomType].new.from(a: 1), CustomType)
144
- expect(T::Coerce[CustomType].new.from(1).a).to be 1
162
+ T.assert_type!(TypeCoerce[CustomType].new.from(a: 1), CustomType)
163
+ expect(TypeCoerce[CustomType].new.from(1).a).to be 1
145
164
 
146
- expect{T::Coerce[UnsupportedCustomType].new.from(1)}.to raise_error(T::CoercionError)
165
+ expect{TypeCoerce[UnsupportedCustomType].new.from(1)}.to raise_error(ArgumentError)
166
+ # CustomType2.new(anything) returns Integer 1; 1.is_a?(CustomType2) == false
167
+ expect{TypeCoerce[CustomType2].new.from(1)}.to raise_error(TypeError)
147
168
  end
148
169
  end
149
170
 
150
171
  context 'when dealing with arries' do
151
172
  it 'coreces correctly' do
152
- expect(T::Coerce[T::Array[Integer]].new.from(nil)).to eql []
153
- expect(T::Coerce[T::Array[Integer]].new.from('not an array')).to eql []
154
- expect(T::Coerce[T::Array[Integer]].new.from('1')).to eql [1]
155
- expect(T::Coerce[T::Array[Integer]].new.from(['1', '2', '3'])).to eql [1, 2, 3]
156
- expect(T::Coerce[T::Array[Integer]].new.from(['1', 'invalid', '3'])).to eql []
157
-
158
- infos = T::Coerce[T::Array[ParamInfo]].new.from(name: 'a', skill_ids: [])
173
+ expect(TypeCoerce[T::Array[Integer]].new.from(nil)).to eql []
174
+ expect(TypeCoerce[T::Array[Integer]].new.from('')).to eql []
175
+ expect{TypeCoerce[T::Array[Integer]].new.from('not an array')}.to raise_error(TypeCoerce::ShapeError)
176
+ expect{TypeCoerce[T::Array[Integer]].new.from('1')}.to raise_error(TypeCoerce::ShapeError)
177
+ expect(TypeCoerce[T::Array[Integer]].new.from(['1', '2', '3'])).to eql [1, 2, 3]
178
+ expect{TypeCoerce[T::Array[Integer]].new.from(['1', 'invalid', '3'])}.to raise_error(TypeCoerce::CoercionError)
179
+ expect{TypeCoerce[T::Array[Integer]].new.from({a: 1})}.to raise_error(TypeCoerce::CoercionError)
180
+
181
+ infos = TypeCoerce[T::Array[ParamInfo]].new.from([{name: 'a', skill_ids: []}])
159
182
  T.assert_type!(infos, T::Array[ParamInfo])
160
183
  expect(infos.first.name).to eql 'a'
161
184
 
162
- infos = T::Coerce[T::Array[ParamInfo]].new.from([{name: 'b', skill_ids: []}])
185
+ infos = TypeCoerce[T::Array[ParamInfo]].new.from([{name: 'b', skill_ids: []}])
163
186
  T.assert_type!(infos, T::Array[ParamInfo])
164
187
  expect(infos.first.name).to eql 'b'
188
+
189
+ expect {
190
+ TypeCoerce[ParamInfo2].new.from({a: nil, b: nil})
191
+ }.to raise_error(TypeError)
192
+ end
193
+ end
194
+
195
+ context 'when dealing with hashes' do
196
+ it 'coreces correctly' do
197
+ expect(TypeCoerce[T::Hash[String, T::Boolean]].new.from({
198
+ a: 'true',
199
+ b: 'false',
200
+ })).to eql({
201
+ 'a' => true,
202
+ 'b' => false,
203
+ })
204
+
205
+ expect(TypeCoerce[HashParams].new.from({
206
+ myhash: {'a' => '1', 'b' => '2'},
207
+ }).myhash).to eql({'a' => 1, 'b' => 2})
208
+
209
+
210
+ expect {
211
+ TypeCoerce[T::Hash[String, T::Boolean]].new.from({
212
+ a: 'invalid',
213
+ b: 'false',
214
+ })
215
+ }.to raise_error(TypeCoerce::CoercionError)
216
+
217
+ expect {
218
+ TypeCoerce[T::Hash[String, Integer]].new.from(1)
219
+ }.to raise_error(TypeCoerce::ShapeError)
220
+ end
221
+ end
222
+
223
+ context 'when dealing with sets' do
224
+ it 'coreces correctly' do
225
+ expect(TypeCoerce[T::Set[Integer]].new.from(
226
+ Set.new(['1', '2', '3'])
227
+ )).to eq Set.new([1, 2, 3])
228
+
229
+ expect {
230
+ TypeCoerce[T::Set[Integer]].new.from(Set.new(['1', 'invalid', '3']))
231
+ }.to raise_error(TypeCoerce::CoercionError)
232
+
233
+ expect {
234
+ TypeCoerce[T::Set[Integer]].new.from(1)
235
+ }.to raise_error(TypeCoerce::ShapeError)
165
236
  end
166
237
  end
167
238
 
@@ -169,7 +240,14 @@ describe T::Coerce do
169
240
  MyType = T.type_alias(T::Boolean)
170
241
 
171
242
  it 'coerces correctly' do
172
- expect(T::Coerce[MyType].new.from('false')).to be false
243
+ expect(TypeCoerce[MyType].new.from('false')).to be false
173
244
  end
174
245
  end
246
+
247
+ it 'works with T.untyped' do
248
+ expect(TypeCoerce[T.untyped].new.from(1)).to eql 1
249
+
250
+ obj = CustomType.new(1)
251
+ expect(TypeCoerce[T::Hash[String, T.untyped]].new.from({a: obj})).to eq({'a' => obj})
252
+ end
175
253
  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,12 +15,12 @@ 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({
19
- users: {id: '1'},
18
+ converted = TypeCoerce[NestedParam].new.from({
19
+ users: [{id: '1'}],
20
20
  params: {
21
- users: {id: '2', valid: 'true'},
21
+ users: [{id: '2', valid: 'true'}],
22
22
  params: {
23
- users: {id: '3', valid: 'true'},
23
+ users: [{id: '3', valid: 'true'}],
24
24
  },
25
25
  },
26
26
  })
@@ -41,17 +41,17 @@ describe T::Coerce do
41
41
  end
42
42
 
43
43
  it 'works with nest T::Array' do
44
+ expect {
45
+ TypeCoerce[T::Array[T.nilable(Integer)]].new.from(['1', 'invalid', '3'])
46
+ }.to raise_error(TypeCoerce::CoercionError)
44
47
  expect(
45
- T::Coerce[T::Array[T.nilable(Integer)]].new.from(['1', 'invalid', '3']),
46
- ).to eql [1, nil, 3]
48
+ TypeCoerce[T::Array[T::Array[Integer]]].new.from([nil])
49
+ ).to eql([[]])
47
50
  expect(
48
- T::Coerce[T::Array[T::Array[Integer]]].new.from(['', '', '']),
49
- ).to eql [[], [], []]
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,19 +59,19 @@ 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[
66
66
  T::Array[
67
- T::Array[T.nilable(User)]
67
+ T::Array[User]
68
68
  ]
69
69
  ]
70
70
  ]
71
71
  ]
72
- ].new.from(nil).flatten).to eql([nil])
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
@@ -0,0 +1,66 @@
1
+ # typed: false
2
+ require 'sorbet-coerce'
3
+ require 'sorbet-runtime'
4
+
5
+ describe TypeCoerce do
6
+ context 'when type errors are soft errors' do
7
+ let(:ignore_error) { Proc.new {} }
8
+
9
+ before(:each) do
10
+ allow(TypeCoerce::Configuration).to receive(
11
+ :raise_coercion_error,
12
+ ).and_return(false)
13
+
14
+ allow(T::Configuration).to receive(
15
+ :inline_type_error_handler,
16
+ ).and_return(ignore_error)
17
+
18
+ allow(T::Configuration).to receive(
19
+ :call_validation_error_handler,
20
+ ).and_return(ignore_error)
21
+
22
+ allow(T::Configuration).to receive(
23
+ :sig_builder_error_handler,
24
+ ).and_return(ignore_error)
25
+ end
26
+
27
+ class ParamsWithSortError < T::Struct
28
+ const :a, Integer
29
+ end
30
+
31
+ class CustomTypeRaisesHardError
32
+ def initialize(value)
33
+ raise StandardError.new('value cannot be 1') if value == 1
34
+ end
35
+ end
36
+
37
+ class CustomTypeDoesNotRiaseHardError
38
+ def self.new(a); 1; end
39
+ end
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
+
49
+ it 'works as expected' do
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])
54
+
55
+ expect {
56
+ TypeCoerce[CustomTypeRaisesHardError].new.from(1)
57
+ }.to raise_error(StandardError)
58
+ expect(TypeCoerce[CustomTypeDoesNotRiaseHardError].new.from(1)).to eql(1)
59
+
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)
63
+ end
64
+ end
65
+ end
66
+ 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.5
4
+ version: 0.2.3
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,11 +118,14 @@ executables: []
118
118
  extensions: []
119
119
  extra_rdoc_files: []
120
120
  files:
121
+ - lib/configuration.rb
121
122
  - lib/private/converter.rb
122
123
  - lib/sorbet-coerce.rb
123
124
  - rbi/sorbet-coerce.rbi
124
125
  - spec/coerce_spec.rb
125
126
  - spec/nested_spec.rb
127
+ - spec/soft_error_spec.rb
128
+ - spec/sorbet_test_cases.rb
126
129
  - spec/spec_helper.rb
127
130
  homepage: https://github.com/chanzuckerberg/sorbet-coerce
128
131
  licenses:
@@ -136,7 +139,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
136
139
  requirements:
137
140
  - - ">="
138
141
  - !ruby/object:Gem::Version
139
- version: 2.3.0
142
+ version: 2.4.0
140
143
  required_rubygems_version: !ruby/object:Gem::Requirement
141
144
  requirements:
142
145
  - - ">="