sorbet-coerce 0.1.6 → 0.2.0

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: e5eefb63dc137b65889fa5dd16b05fcf3bef550474d7f7f175746ccca96e640d
4
+ data.tar.gz: 0ca052e76b580d9979d24444b8bee8d564a49c6d2162acd36469fbd205cb6354
5
5
  SHA512:
6
- metadata.gz: d8f9f8e5086248a4db00ef26a0285fb9363817f552f77658d0ca8c0b5c373221df6c67116b9735fcd33d41831f6c49d139dfd1608e44adbe0cd5168335b863c5
7
- data.tar.gz: a159fd6c75c112e71ad4e801ae759b01051f1a1e0ef2d02f875e40b4444b25b098a9049f3ab7dea596b5373c5e86759ba3a78da622978ef8147ba4883a51c444
6
+ metadata.gz: afb06e4b35972579d9ff0ffb0d9eb48c1de7465911b0f56a07a1d7dcda528f70d525834f5ac81c300394132ae5e8fec7d614814c900710778fa13296ed2db7f3
7
+ data.tar.gz: eecba6f2089a944984b2e6ba595ad21bbff2d11bb3fc56d38532fff94f029fe6beac3e1fce25272d46162e893d3040a548fe4240058db8a258900ee815acb946
@@ -0,0 +1,15 @@
1
+ # typed: true
2
+ require 'sorbet-runtime'
3
+
4
+ module T::Coerce
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
+ T::Coerce::Configuration.raise_coercion_error = true
@@ -5,8 +5,6 @@ require 'polyfill'
5
5
 
6
6
  using Polyfill(Hash: %w[#slice])
7
7
 
8
- module T; end
9
-
10
8
  module T::Private
11
9
  class Converter
12
10
  extend T::Sig
@@ -22,12 +20,12 @@ module T::Private
22
20
  ], T.untyped)
23
21
 
24
22
  protected
25
- sig { params(value: T.untyped, type: T.untyped).returns(T.untyped) }
26
- def _convert(value, type)
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)
27
25
  if type.is_a?(T::Types::TypedArray)
28
- _convert_to_a(value, type.type)
26
+ _convert_to_a(value, type.type, raise_coercion_error)
29
27
  elsif type.is_a?(T::Types::Simple)
30
- _convert(value, type.raw_type)
28
+ _convert(value, type.raw_type, raise_coercion_error)
31
29
  elsif type.is_a?(T::Types::Union)
32
30
  true_idx = T.let(nil, T.nilable(Integer))
33
31
  false_idx = T.let(nil, T.nilable(Integer))
@@ -49,23 +47,23 @@ module T::Private
49
47
  )
50
48
 
51
49
  if !true_idx.nil? && !false_idx.nil?
52
- _convert_simple(value, T::Boolean)
50
+ _convert_simple(value, T::Boolean, raise_coercion_error)
53
51
  else
54
- _convert(value, type.types[nil_idx == 0 ? 1 : 0])
52
+ _convert(value, type.types[nil_idx == 0 ? 1 : 0], raise_coercion_error)
55
53
  end
56
54
  elsif Object.const_defined?('T::Private::Types::TypeAlias') &&
57
55
  type.is_a?(T::Private::Types::TypeAlias)
58
- _convert(value, type.aliased_type)
56
+ _convert(value, type.aliased_type, raise_coercion_error)
59
57
  elsif type < T::Struct
60
- args = _build_args(value, type.props)
58
+ args = _build_args(value, type, raise_coercion_error)
61
59
  type.new(args)
62
60
  else
63
- _convert_simple(value, type)
61
+ _convert_simple(value, type, raise_coercion_error)
64
62
  end
65
63
  end
66
64
 
67
- sig { params(value: T.untyped, type: T.untyped).returns(T.untyped) }
68
- def _convert_simple(value, type)
65
+ sig { params(value: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
66
+ def _convert_simple(value, type, raise_coercion_error)
69
67
  return nil if _nil_like?(value, type)
70
68
 
71
69
  safe_type_rule = T.let(nil, T.untyped)
@@ -81,29 +79,41 @@ module T::Private
81
79
  end
82
80
  SafeType::coerce(value, safe_type_rule)
83
81
  rescue SafeType::EmptyValueError, SafeType::CoercionError
84
- value
82
+ if raise_coercion_error
83
+ raise T::Coerce::CoercionError.new(value, type)
84
+ else
85
+ nil
86
+ end
85
87
  rescue SafeType::InvalidRuleError
86
88
  type.new(value)
87
89
  end
88
90
 
89
- sig { params(ary: T.untyped, type: T.untyped).returns(T.untyped) }
90
- def _convert_to_a(ary, type)
91
+ sig { params(ary: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
92
+ def _convert_to_a(ary, type, raise_coercion_error)
91
93
  return [] if _nil_like?(ary, type)
92
94
 
93
- # Checked by the T.let at root
94
- ary.respond_to?(:map) ? ary.map { |value| _convert(value, type) } : ary
95
+ unless ary.respond_to?(:map)
96
+ raise T::Coerce::ShapeError.new(ary, type)
97
+ end
98
+
99
+ ary.map { |value| _convert(value, type, raise_coercion_error) }
95
100
  end
96
101
 
97
- sig { params(args: T.untyped, props: T.untyped).returns(T.untyped) }
98
- def _build_args(args, props)
102
+ sig { params(args: T.untyped, type: T.untyped, raise_coercion_error: T::Boolean).returns(T.untyped) }
103
+ def _build_args(args, type, raise_coercion_error)
99
104
  return {} if _nil_like?(args, Hash)
100
105
 
106
+ unless args.respond_to?(:each_pair)
107
+ raise T::Coerce::ShapeError.new(args, type)
108
+ end
109
+
110
+ props = type.props
101
111
  args.map { |name, value|
102
112
  key = name.to_sym
103
113
  [
104
114
  key,
105
115
  (!props.include?(key) || value.nil?) ?
106
- nil : _convert(value, props[key][:type]),
116
+ nil : _convert(value, props[key][:type], raise_coercion_error),
107
117
  ]
108
118
  }.to_h.slice(*props.keys)
109
119
  end
@@ -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 T::Coerce
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(T::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 = T::Coerce::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
@@ -6,8 +6,8 @@ module T
6
6
 
7
7
  Elem = type_member
8
8
 
9
- sig { params(args: T.untyped).returns(Elem) }
10
- def from(args); end
9
+ sig { params(args: T.untyped, raise_value_error: T.nilable(T::Boolean)).returns(Elem) }
10
+ def from(args, raise_value_error: nil); end
11
11
  end
12
12
 
13
13
  module Private
@@ -136,19 +136,19 @@ describe T::Coerce do
136
136
  end
137
137
 
138
138
  it 'coreces correctly' do
139
- expect{T::Coerce[Integer].new.from(nil)}.to raise_error(T::CoercionError)
139
+ expect{T::Coerce[Integer].new.from(nil)}.to raise_error(TypeError)
140
140
  expect(T::Coerce[T.nilable(Integer)].new.from(nil) || 1).to eql 1
141
141
  expect(T::Coerce[Integer].new.from(2)).to eql 2
142
142
  expect(T::Coerce[Integer].new.from('1.0')).to eql 1
143
143
 
144
- expect{T::Coerce[T.nilable(Integer)].new.from('invalid integer string')}.to raise_error(T::CoercionError)
144
+ expect{T::Coerce[T.nilable(Integer)].new.from('invalid integer string')}.to raise_error(T::Coerce::CoercionError)
145
145
  expect(T::Coerce[Float].new.from('1.0')).to eql 1.0
146
146
 
147
147
  expect(T::Coerce[T::Boolean].new.from('false')).to be false
148
148
  expect(T::Coerce[T::Boolean].new.from('true')).to be true
149
149
 
150
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)
151
+ expect{T::Coerce[T.nilable(Integer)].new.from([])}.to raise_error(T::Coerce::CoercionError)
152
152
  expect(T::Coerce[T.nilable(String)].new.from('')).to eql ''
153
153
  end
154
154
  end
@@ -159,7 +159,8 @@ describe T::Coerce do
159
159
  expect(T::Coerce[CustomType].new.from(1).a).to be 1
160
160
 
161
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)
162
+ # CustomType2.new(anything) returns Integer 1; 1.is_a?(CustomType2) == false
163
+ expect{T::Coerce[CustomType2].new.from(1)}.to raise_error(TypeError)
163
164
  end
164
165
  end
165
166
 
@@ -167,11 +168,11 @@ describe T::Coerce do
167
168
  it 'coreces correctly' do
168
169
  expect(T::Coerce[T::Array[Integer]].new.from(nil)).to eql []
169
170
  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)
171
+ expect{T::Coerce[T::Array[Integer]].new.from('not an array')}.to raise_error(T::Coerce::ShapeError)
172
+ expect{T::Coerce[T::Array[Integer]].new.from('1')}.to raise_error(T::Coerce::ShapeError)
172
173
  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)
174
+ expect{T::Coerce[T::Array[Integer]].new.from(['1', 'invalid', '3'])}.to raise_error(T::Coerce::CoercionError)
175
+ expect{T::Coerce[T::Array[Integer]].new.from({a: 1})}.to raise_error(T::Coerce::CoercionError)
175
176
 
176
177
  infos = T::Coerce[T::Array[ParamInfo]].new.from([{name: 'a', skill_ids: []}])
177
178
  T.assert_type!(infos, T::Array[ParamInfo])
@@ -180,6 +181,10 @@ describe T::Coerce do
180
181
  infos = T::Coerce[T::Array[ParamInfo]].new.from([{name: 'b', skill_ids: []}])
181
182
  T.assert_type!(infos, T::Array[ParamInfo])
182
183
  expect(infos.first.name).to eql 'b'
184
+
185
+ expect {
186
+ T::Coerce[ParamInfo2].new.from({a: nil, b: nil})
187
+ }.to raise_error(TypeError)
183
188
  end
184
189
  end
185
190
 
@@ -43,7 +43,7 @@ describe T::Coerce do
43
43
  it 'works with nest T::Array' do
44
44
  expect {
45
45
  T::Coerce[T::Array[T.nilable(Integer)]].new.from(['1', 'invalid', '3'])
46
- }.to raise_error(T::CoercionError)
46
+ }.to raise_error(T::Coerce::CoercionError)
47
47
  expect(
48
48
  T::Coerce[T::Array[T::Array[Integer]]].new.from([nil])
49
49
  ).to eql([[]])
@@ -7,6 +7,10 @@ describe T::Coerce do
7
7
  let(:ignore_error) { Proc.new {} }
8
8
 
9
9
  before(:each) do
10
+ allow(T::Coerce::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
+ T::Coerce[Integer].new.from(invalid_arg, raise_coercion_error: true)
46
+ }.to raise_error(T::Coerce::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(T::Coerce[Integer].new.from(invalid_arg)).to eql(nil)
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])
43
54
 
44
55
  expect {
45
56
  T::Coerce[CustomTypeRaisesHardError].new.from(1)
46
57
  }.to raise_error(StandardError)
47
58
  expect(T::Coerce[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(T::Coerce[ParamsWithSortError].new.from({a: invalid_arg}).a).to eql(nil)
51
63
  end
52
64
  end
53
65
  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.6
4
+ version: 0.2.0
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,6 +118,7 @@ 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
@@ -137,7 +138,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
138
  requirements:
138
139
  - - ">="
139
140
  - !ruby/object:Gem::Version
140
- version: 2.3.0
141
+ version: 2.4.0
141
142
  required_rubygems_version: !ruby/object:Gem::Requirement
142
143
  requirements:
143
144
  - - ">="