sorbet-coerce 0.2.4 → 0.4.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: 8c5679d22c95a68cf624bef8a9d3f0c62b9b33f158b439b4115b2cb2f18dc508
4
- data.tar.gz: f74881e16d72bb4b07b5de8072ef1f10d7794e8e6af249f9edcf132628a3a0e2
3
+ metadata.gz: 7503ed812a03fc5478701f1821be08195b10945d700dd27cda21088aa2e01f55
4
+ data.tar.gz: 6fd2a30012fad3af9f5fceee3de2a60c03bc7fb3f7fccdf42223b669c0e8c409
5
5
  SHA512:
6
- metadata.gz: 5f0a4df69764c02a35aba2101740beb6445bc77dec5f5edea12c31c7db18837696153e4f41ab5604ba374a610a203377ec38f621c2d93f5e58a57d72e6e05b4b
7
- data.tar.gz: 334a49158e79661994d826dbd5845b071260d66f5ffeae49d45d22c6c62a2deeeccb11fe3efe8e616856c2eb3bf60c12031795c4cace5817b87e6ca7ebfa633f
6
+ metadata.gz: ebbeb777352a39fe67575ee44c12ccb4ad1433ff7504bd76a9f9b86d91aef09a6e120496763254f433af783e2ab6dfec17b7822651db1bf563f9b7f223e8149c
7
+ data.tar.gz: 7d536c441a095d32af634b7ad362750e9c6d4f95789cf9cfdeb3d77aa0a90485ca4622517d3138cf7815c9eadc0e282ef7e75711b5fde27d4bfcca75b561dba8
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: false
2
2
  module SafeType
3
3
  class CoercionError < StandardError; end
4
4
  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
4
  module TypeCoerce
7
- class CoercionError < SafeType::CoercionError; end
8
- class ShapeError < SafeType::CoercionError; end
9
-
10
- define_singleton_method(:[]) do |type|
11
- Class.new(TypeCoerce::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 = TypeCoerce::Configuration.raise_coercion_error
17
- end
18
-
19
- T.send('let', send('_convert', args, type, raise_coercion_error), type)
20
- end
21
- end
5
+ def self.[](type)
6
+ TypeCoerce::Converter.new(type)
22
7
  end
23
8
  end
@@ -0,0 +1,183 @@
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
+ elsif type.respond_to?(:<) && type < T::Enum
104
+ return value if value.is_a?(type)
105
+
106
+ _convert_enum(value, type, raise_coercion_error)
107
+ else
108
+ return value if value.is_a?(type)
109
+
110
+ _convert_simple(value, type, raise_coercion_error)
111
+ end
112
+ end
113
+
114
+ def _convert_enum(value, type, raise_coercion_error)
115
+ if raise_coercion_error
116
+ type.deserialize(value)
117
+ else
118
+ type.try_deserialize(value)
119
+ end
120
+ rescue KeyError
121
+ raise TypeCoerce::CoercionError.new(value, type)
122
+ end
123
+
124
+ def _convert_simple(value, type, raise_coercion_error)
125
+ return nil if _nil_like?(value, type)
126
+
127
+ safe_type_rule = T.let(nil, T.untyped)
128
+
129
+ if type == T::Boolean
130
+ safe_type_rule = SafeType::Boolean.strict
131
+ elsif value.is_a?(type)
132
+ return value
133
+ elsif PRIMITIVE_TYPES.include?(type)
134
+ safe_type_rule = SafeType.const_get(type.name).strict
135
+ else
136
+ safe_type_rule = type
137
+ end
138
+
139
+ if safe_type_rule.is_a?(SafeType::Rule)
140
+ SafeType::coerce(value, safe_type_rule)
141
+ else
142
+ type.new(value)
143
+ end
144
+ rescue SafeType::EmptyValueError, SafeType::CoercionError
145
+ if raise_coercion_error
146
+ raise TypeCoerce::CoercionError.new(value, type)
147
+ else
148
+ nil
149
+ end
150
+ end
151
+
152
+ def _convert_to_a(ary, type, raise_coercion_error)
153
+ return [] if _nil_like?(ary, type)
154
+
155
+ unless ary.respond_to?(:map)
156
+ raise TypeCoerce::ShapeError.new(ary, type)
157
+ end
158
+
159
+ ary.map { |value| _convert(value, type, raise_coercion_error) }
160
+ end
161
+
162
+ def _build_args(args, type, raise_coercion_error)
163
+ return {} if _nil_like?(args, Hash)
164
+
165
+ unless args.respond_to?(:each_pair)
166
+ raise TypeCoerce::ShapeError.new(args, type)
167
+ end
168
+
169
+ props = type.props
170
+ args.map { |name, value|
171
+ key = name.to_sym
172
+ [
173
+ key,
174
+ (!props.include?(key) || value.nil?) ?
175
+ nil : _convert(value, props[key][:type], raise_coercion_error),
176
+ ]
177
+ }.to_h.slice(*props.keys)
178
+ end
179
+
180
+ def _nil_like?(value, type)
181
+ value.nil? || (value == '' && type != String)
182
+ end
183
+ end
data/spec/coerce_spec.rb CHANGED
@@ -35,6 +35,17 @@ describe TypeCoerce do
35
35
  const :myhash, T::Hash[String, Integer], default: Hash['a' => 1]
36
36
  end
37
37
 
38
+ class TestEnum < T::Enum
39
+ enums do
40
+ Test = new
41
+ Other = new
42
+ end
43
+ end
44
+
45
+ class WithEnum < T::Struct
46
+ const :myenum, TestEnum
47
+ end
48
+
38
49
  class CustomType
39
50
  attr_reader :a
40
51
 
@@ -99,6 +110,7 @@ describe TypeCoerce do
99
110
  expect(param.info.name).to eql 'mango'
100
111
  expect(param.info.skill_ids).to eql [123, 456]
101
112
  expect(param.opt.notes).to eql []
113
+ expect(TypeCoerce[Param].new.from(param)).to eq(param)
102
114
 
103
115
  expect(param2.id).to eql 2
104
116
  expect(param2.info.name).to eql 'honeydew'
@@ -163,8 +175,10 @@ describe TypeCoerce do
163
175
 
164
176
  context 'when given custom types' do
165
177
  it 'coerces correctly' do
166
- T.assert_type!(TypeCoerce[CustomType].new.from(a: 1), CustomType)
167
- expect(TypeCoerce[CustomType].new.from(1).a).to be 1
178
+ obj = TypeCoerce[CustomType].new.from(1)
179
+ T.assert_type!(obj, CustomType)
180
+ expect(obj.a).to be 1
181
+ expect(TypeCoerce[CustomType].new.from(obj)).to be obj
168
182
 
169
183
  expect{TypeCoerce[UnsupportedCustomType].new.from(1)}.to raise_error(ArgumentError)
170
184
  # CustomType2.new(anything) returns Integer 1; 1.is_a?(CustomType2) == false
@@ -251,6 +265,24 @@ describe TypeCoerce do
251
265
  end
252
266
  end
253
267
 
268
+ context 'when dealing with enums' do
269
+ it 'coerces a serialized enum correctly' do
270
+ coerced = TypeCoerce[WithEnum].new.from(myenum: "test")
271
+ expect(coerced.myenum).to eq(TestEnum::Test)
272
+ end
273
+
274
+ it 'handles a real enum correctly' do
275
+ coerced = TypeCoerce[WithEnum].new.from(myenum: TestEnum::Test)
276
+ expect(coerced.myenum).to eq(TestEnum::Test)
277
+ end
278
+
279
+ it 'handles bad enum' do
280
+ expect {
281
+ TypeCoerce[WithEnum].new.from(myenum: "bad_key")
282
+ }.to raise_error(TypeCoerce::CoercionError)
283
+ end
284
+ end
285
+
254
286
  it 'works with T.untyped' do
255
287
  expect(TypeCoerce[T.untyped].new.from(1)).to eql 1
256
288
 
@@ -4,6 +4,12 @@ require 'sorbet-runtime'
4
4
 
5
5
  describe TypeCoerce do
6
6
  context 'when type errors are soft errors' do
7
+ class SoftErrorTestEnum < T::Enum
8
+ enums do
9
+ Other = new
10
+ end
11
+ end
12
+
7
13
  let(:ignore_error) { Proc.new {} }
8
14
 
9
15
  before(:each) do
@@ -48,6 +54,7 @@ describe TypeCoerce do
48
54
 
49
55
  it 'works as expected' do
50
56
  expect(TypeCoerce[Integer].new.from(invalid_arg)).to eql(nil)
57
+ expect(TypeCoerce[SoftErrorTestEnum].new.from('bad_key')).to eql(nil)
51
58
 
52
59
  expect{TypeCoerce[T::Array[Integer]].new.from(1)}.to raise_error(TypeCoerce::ShapeError)
53
60
  expect(TypeCoerce[T::Array[Integer]].new.from({a: 1})).to eql([nil])
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.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chan Zuckerberg Initiative
@@ -10,20 +10,6 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2019-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: sorbet
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 0.4.4704
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: 0.4.4704
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: polyfill
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -72,44 +58,58 @@ dependencies:
72
58
  - - ">="
73
59
  - !ruby/object:Gem::Version
74
60
  version: 0.4.4704
61
+ - !ruby/object:Gem::Dependency
62
+ name: sorbet
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 0.4.4704
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 0.4.4704
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: rspec
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - "~>"
79
+ - - ">="
80
80
  - !ruby/object:Gem::Version
81
81
  version: '3.8'
82
- - - ">="
82
+ - - "~>"
83
83
  - !ruby/object:Gem::Version
84
84
  version: '3.8'
85
85
  type: :development
86
86
  prerelease: false
87
87
  version_requirements: !ruby/object:Gem::Requirement
88
88
  requirements:
89
- - - "~>"
89
+ - - ">="
90
90
  - !ruby/object:Gem::Version
91
91
  version: '3.8'
92
- - - ">="
92
+ - - "~>"
93
93
  - !ruby/object:Gem::Version
94
94
  version: '3.8'
95
95
  - !ruby/object:Gem::Dependency
96
96
  name: byebug
97
97
  requirement: !ruby/object:Gem::Requirement
98
98
  requirements:
99
- - - "~>"
99
+ - - ">="
100
100
  - !ruby/object:Gem::Version
101
101
  version: 11.0.1
102
- - - ">="
102
+ - - "~>"
103
103
  - !ruby/object:Gem::Version
104
104
  version: 11.0.1
105
105
  type: :development
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
- - - "~>"
109
+ - - ">="
110
110
  - !ruby/object:Gem::Version
111
111
  version: 11.0.1
112
- - - ">="
112
+ - - "~>"
113
113
  - !ruby/object:Gem::Version
114
114
  version: 11.0.1
115
115
  description:
@@ -119,10 +119,9 @@ extensions: []
119
119
  extra_rdoc_files: []
120
120
  files:
121
121
  - lib/bundled_rbi/sorbet-coerce.rbi
122
- - lib/configuration.rb
123
- - lib/private/converter.rb
124
- - lib/private/coverter.rbi
125
122
  - lib/sorbet-coerce.rb
123
+ - lib/sorbet-coerce/configuration.rb
124
+ - lib/sorbet-coerce/converter.rb
126
125
  - spec/coerce_spec.rb
127
126
  - spec/nested_spec.rb
128
127
  - spec/soft_error_spec.rb
@@ -147,8 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
146
  - !ruby/object:Gem::Version
148
147
  version: '0'
149
148
  requirements: []
150
- rubyforge_project:
151
- rubygems_version: 2.7.7
149
+ rubygems_version: 3.0.8
152
150
  signing_key:
153
151
  specification_version: 4
154
152
  summary: A type coercion lib works with Sorbet's static type checker and type definitions;
@@ -1,149 +0,0 @@
1
- # typed: strict
2
- require 'safe_type'
3
- require 'set'
4
- require 'sorbet-runtime'
5
- require 'polyfill'
6
-
7
- using Polyfill(Hash: %w[#slice])
8
-
9
- module TypeCoerce; end
10
-
11
- module TypeCoerce::Private
12
- class Converter
13
- extend T::Sig
14
-
15
- PRIMITIVE_TYPES = T.let(::Set[
16
- Date,
17
- DateTime,
18
- Float,
19
- Integer,
20
- String,
21
- Symbol,
22
- Time,
23
- ], T.untyped)
24
-
25
- protected
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))
34
- elsif type.is_a?(T::Types::Simple)
35
- _convert(value, type.raw_type, raise_coercion_error)
36
- elsif type.is_a?(T::Types::Union)
37
- true_idx = T.let(nil, T.nilable(Integer))
38
- false_idx = T.let(nil, T.nilable(Integer))
39
- nil_idx = T.let(nil, T.nilable(Integer))
40
-
41
- type.types.each_with_index do |t, i|
42
- nil_idx = i if t.is_a?(T::Types::Simple) && t.raw_type == NilClass
43
- true_idx = i if t.is_a?(T::Types::Simple) && t.raw_type == TrueClass
44
- false_idx = i if t.is_a?(T::Types::Simple) && t.raw_type == FalseClass
45
- end
46
-
47
- raise ArgumentError.new(
48
- 'the only supported union types are T.nilable and T::Boolean',
49
- ) unless (
50
- (!true_idx.nil? && !false_idx.nil? && !nil_idx.nil?) || # T.nilable(T::Boolean)
51
- (type.types.length == 2 && (
52
- !nil_idx.nil? || (!true_idx.nil? && !false_idx.nil?) # T.nilable || T::Boolean
53
- ))
54
- )
55
-
56
- if !true_idx.nil? && !false_idx.nil?
57
- _convert_simple(value, T::Boolean, raise_coercion_error)
58
- else
59
- _convert(value, type.types[nil_idx == 0 ? 1 : 0], raise_coercion_error)
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
74
- elsif Object.const_defined?('T::Private::Types::TypeAlias') &&
75
- type.is_a?(T::Private::Types::TypeAlias)
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)
79
- type.new(args)
80
- else
81
- _convert_simple(value, type, raise_coercion_error)
82
- end
83
- end
84
-
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)
87
- return nil if _nil_like?(value, type)
88
-
89
- safe_type_rule = T.let(nil, T.untyped)
90
-
91
- if type == T::Boolean
92
- safe_type_rule = SafeType::Boolean.strict
93
- elsif value.is_a?(type)
94
- return value
95
- elsif PRIMITIVE_TYPES.include?(type)
96
- safe_type_rule = SafeType.const_get(type.name).strict
97
- else
98
- safe_type_rule = type
99
- end
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
106
- rescue SafeType::EmptyValueError, SafeType::CoercionError
107
- if raise_coercion_error
108
- raise TypeCoerce::CoercionError.new(value, type)
109
- else
110
- nil
111
- end
112
- end
113
-
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)
116
- return [] if _nil_like?(ary, type)
117
-
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) }
123
- end
124
-
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)
127
- return {} if _nil_like?(args, Hash)
128
-
129
- unless args.respond_to?(:each_pair)
130
- raise TypeCoerce::ShapeError.new(args, type)
131
- end
132
-
133
- props = type.props
134
- args.map { |name, value|
135
- key = name.to_sym
136
- [
137
- key,
138
- (!props.include?(key) || value.nil?) ?
139
- nil : _convert(value, props[key][:type], raise_coercion_error),
140
- ]
141
- }.to_h.slice(*props.keys)
142
- end
143
-
144
- sig { params(value: T.untyped, type: T.untyped).returns(T::Boolean) }
145
- def _nil_like?(value, type)
146
- value.nil? || (value == '' && type != String)
147
- end
148
- end
149
- end
@@ -1,10 +0,0 @@
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