sorbet-coerce 0.2.2 → 0.2.7

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