sorbet-coerce 0.1.5 → 0.1.6

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: 9749ed0f8347a42911400212d8a30279876e10b1983daabc16167f819fdd76bd
4
+ data.tar.gz: 2866bbe6175228c14b563928bf44521608486ca7c1cb2aa6dc4bbf19aafd0bcf
5
5
  SHA512:
6
- metadata.gz: b08186d2c9c95bc3933f15895e23031a09fd49af3d16886c82433831e5d395c4a496c9e58a2b22c42630d01b925d151beca01656446a3d10b0ca46acab83dcad
7
- data.tar.gz: 9dcec07cc34048713d4ea627b1afac67c6885e82e8623ecad4b2f36af763c70c13e7d79a6d295c36cb2d502f62837b7df5f304c29d845f40d5be2b75660b64b7
6
+ metadata.gz: d8f9f8e5086248a4db00ef26a0285fb9363817f552f77658d0ca8c0b5c373221df6c67116b9735fcd33d41831f6c49d139dfd1608e44adbe0cd5168335b863c5
7
+ data.tar.gz: a159fd6c75c112e71ad4e801ae759b01051f1a1e0ef2d02f875e40b4444b25b098a9049f3ab7dea596b5373c5e86759ba3a78da622978ef8147ba4883a51c444
@@ -58,11 +58,7 @@ module T::Private
58
58
  _convert(value, type.aliased_type)
59
59
  elsif type < T::Struct
60
60
  args = _build_args(value, type.props)
61
- begin
62
- type.new(args)
63
- rescue
64
- nil
65
- end
61
+ type.new(args)
66
62
  else
67
63
  _convert_simple(value, type)
68
64
  end
@@ -70,7 +66,8 @@ module T::Private
70
66
 
71
67
  sig { params(value: T.untyped, type: T.untyped).returns(T.untyped) }
72
68
  def _convert_simple(value, type)
73
- return nil if value.nil?
69
+ return nil if _nil_like?(value, type)
70
+
74
71
  safe_type_rule = T.let(nil, T.untyped)
75
72
 
76
73
  if type == T::Boolean
@@ -84,30 +81,23 @@ module T::Private
84
81
  end
85
82
  SafeType::coerce(value, safe_type_rule)
86
83
  rescue SafeType::EmptyValueError, SafeType::CoercionError
87
- nil
84
+ value
88
85
  rescue SafeType::InvalidRuleError
89
- begin
90
- type.new(value)
91
- rescue
92
- nil
93
- end
86
+ type.new(value)
94
87
  end
95
88
 
96
89
  sig { params(ary: T.untyped, type: T.untyped).returns(T.untyped) }
97
90
  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
- []
91
+ return [] if _nil_like?(ary, type)
92
+
93
+ # Checked by the T.let at root
94
+ ary.respond_to?(:map) ? ary.map { |value| _convert(value, type) } : ary
106
95
  end
107
96
 
108
97
  sig { params(args: T.untyped, props: T.untyped).returns(T.untyped) }
109
98
  def _build_args(args, props)
110
- return {} if args.nil?
99
+ return {} if _nil_like?(args, Hash)
100
+
111
101
  args.map { |name, value|
112
102
  key = name.to_sym
113
103
  [
@@ -117,5 +107,10 @@ module T::Private
117
107
  ]
118
108
  }.to_h.slice(*props.keys)
119
109
  end
110
+
111
+ sig { params(value: T.untyped, type: T.untyped).returns(T::Boolean) }
112
+ def _nil_like?(value, type)
113
+ value.nil? || (value == '' && type != String)
114
+ end
120
115
  end
121
116
  end
data/spec/coerce_spec.rb CHANGED
@@ -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,10 @@ 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
+
26
30
  class CustomType
27
31
  attr_reader :a
28
32
 
@@ -31,6 +35,10 @@ describe T::Coerce do
31
35
  end
32
36
  end
33
37
 
38
+ class CustomType2
39
+ def self.new(a); 1; end
40
+ end
41
+
34
42
  class UnsupportedCustomType
35
43
  # Does not respond to new
36
44
  end
@@ -82,7 +90,7 @@ describe T::Coerce do
82
90
  expect(param.info.lvl).to eql 100
83
91
  expect(param.info.name).to eql 'mango'
84
92
  expect(param.info.skill_ids).to eql [123, 456]
85
- expect(param.opt).to be nil # missing notes
93
+ expect(param.opt.notes).to eql []
86
94
 
87
95
  expect(param2.id).to eql 2
88
96
  expect(param2.info.name).to eql 'honeydew'
@@ -92,15 +100,18 @@ describe T::Coerce do
92
100
  expect(param2.opt.b).to eql 2
93
101
  expect(param2.opt.notes).to eql []
94
102
 
95
- expect(
96
- T::Coerce[T.nilable(Param)].new.from({
103
+ expect {
104
+ T::Coerce[Param].new.from({
97
105
  id: 3,
98
106
  info: {
99
107
  # missing required name
100
108
  lvl: 2,
101
109
  },
102
110
  })
103
- ).to be nil
111
+ }.to raise_error(ArgumentError)
112
+
113
+ expect(T::Coerce[DefaultParams].new.from(nil).a).to be 1
114
+ expect(T::Coerce[DefaultParams].new.from('').a).to be 1
104
115
  end
105
116
  end
106
117
 
@@ -130,11 +141,15 @@ describe T::Coerce do
130
141
  expect(T::Coerce[Integer].new.from(2)).to eql 2
131
142
  expect(T::Coerce[Integer].new.from('1.0')).to eql 1
132
143
 
133
- expect(T::Coerce[T.nilable(Integer)].new.from('invalid integer string')).to be nil
144
+ expect{T::Coerce[T.nilable(Integer)].new.from('invalid integer string')}.to raise_error(T::CoercionError)
134
145
  expect(T::Coerce[Float].new.from('1.0')).to eql 1.0
135
146
 
136
147
  expect(T::Coerce[T::Boolean].new.from('false')).to be false
137
148
  expect(T::Coerce[T::Boolean].new.from('true')).to be true
149
+
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 ''
138
153
  end
139
154
  end
140
155
 
@@ -143,19 +158,22 @@ describe T::Coerce do
143
158
  T.assert_type!(T::Coerce[CustomType].new.from(a: 1), CustomType)
144
159
  expect(T::Coerce[CustomType].new.from(1).a).to be 1
145
160
 
146
- expect{T::Coerce[UnsupportedCustomType].new.from(1)}.to raise_error(T::CoercionError)
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)
147
163
  end
148
164
  end
149
165
 
150
166
  context 'when dealing with arries' do
151
167
  it 'coreces correctly' do
152
168
  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]
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)
155
172
  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 []
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)
157
175
 
158
- infos = T::Coerce[T::Array[ParamInfo]].new.from(name: 'a', skill_ids: [])
176
+ infos = T::Coerce[T::Array[ParamInfo]].new.from([{name: 'a', skill_ids: []}])
159
177
  T.assert_type!(infos, T::Array[ParamInfo])
160
178
  expect(infos.first.name).to eql 'a'
161
179
 
data/spec/nested_spec.rb CHANGED
@@ -16,11 +16,11 @@ describe T::Coerce do
16
16
 
17
17
  it 'works with nest T::Struct' do
18
18
  converted = T::Coerce[NestedParam].new.from({
19
- users: {id: '1'},
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,12 +41,12 @@ describe T::Coerce do
41
41
  end
42
42
 
43
43
  it 'works with nest T::Array' do
44
+ expect {
45
+ T::Coerce[T::Array[T.nilable(Integer)]].new.from(['1', 'invalid', '3'])
46
+ }.to raise_error(T::CoercionError)
44
47
  expect(
45
- T::Coerce[T::Array[T.nilable(Integer)]].new.from(['1', 'invalid', '3']),
46
- ).to eql [1, nil, 3]
47
- expect(
48
- T::Coerce[T::Array[T::Array[Integer]]].new.from(['', '', '']),
49
- ).to eql [[], [], []]
48
+ T::Coerce[T::Array[T::Array[Integer]]].new.from([nil])
49
+ ).to eql([[]])
50
50
  expect(
51
51
  T::Coerce[T::Array[T::Array[Integer]]].new.from([['1'], ['2'], ['3']]),
52
52
  ).to eql [[1], [2], [3]]
@@ -64,12 +64,12 @@ describe T::Coerce do
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
74
  expect(T::Coerce[
75
75
  T.nilable(T::Array[T.nilable(T::Array[T.nilable(User)])])
@@ -0,0 +1,54 @@
1
+ # typed: false
2
+ require 'sorbet-coerce'
3
+ require 'sorbet-runtime'
4
+
5
+ describe T::Coerce do
6
+ context 'when type errors are soft errors' do
7
+ let(:ignore_error) { Proc.new {} }
8
+
9
+ before(:each) do
10
+ allow(T::Configuration).to receive(
11
+ :inline_type_error_handler,
12
+ ).and_return(ignore_error)
13
+
14
+ allow(T::Configuration).to receive(
15
+ :call_validation_error_handler,
16
+ ).and_return(ignore_error)
17
+
18
+ allow(T::Configuration).to receive(
19
+ :sig_builder_error_handler,
20
+ ).and_return(ignore_error)
21
+ end
22
+
23
+ class ParamsWithSortError < T::Struct
24
+ const :a, Integer
25
+ end
26
+
27
+ class CustomTypeRaisesHardError
28
+ def initialize(value)
29
+ raise StandardError.new('value cannot be 1') if value == 1
30
+ end
31
+ end
32
+
33
+ class CustomTypeDoesNotRiaseHardError
34
+ def self.new(a); 1; end
35
+ end
36
+
37
+ 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]])
43
+
44
+ expect {
45
+ T::Coerce[CustomTypeRaisesHardError].new.from(1)
46
+ }.to raise_error(StandardError)
47
+ expect(T::Coerce[CustomTypeDoesNotRiaseHardError].new.from(1)).to eql(1)
48
+
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)
51
+ end
52
+ end
53
+ end
54
+ end
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.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chan Zuckerberg Initiative
@@ -123,6 +123,7 @@ files:
123
123
  - rbi/sorbet-coerce.rbi
124
124
  - spec/coerce_spec.rb
125
125
  - spec/nested_spec.rb
126
+ - spec/soft_error_spec.rb
126
127
  - spec/spec_helper.rb
127
128
  homepage: https://github.com/chanzuckerberg/sorbet-coerce
128
129
  licenses: