sorbet-coerce 0.1.4 → 0.1.5

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: 4605f60de1cb850becbead372463c72a479e75c4733744d22241ddcfe735620f
4
- data.tar.gz: 367859276f17ed037a1b86c472932d686e89e6bb643859ff57ac39d22e3a6f7d
3
+ metadata.gz: 5e0850189628e40cec7b240cd1a12c5abf995369f363444b7f5ecd398239bfa4
4
+ data.tar.gz: 06756e0942061e2feb84958027a666a86b52345000b797a5cab3ceaccbfce0f9
5
5
  SHA512:
6
- metadata.gz: c38e66605e0d58224aa5ee7cd9705d6dd62abdaedefc265752f4723bfdf0c1611af8f7906691484a73a48f35113bb457a6dafc5ec640e68f3921b12166f90385
7
- data.tar.gz: 841c74dfed4c9860125865ead6649d1e80799a7d3a81e88c4d7878e549718080744abd261dcddb0e23e89521e33448594ddbbd33bc44842853a1bb79a153af20
6
+ metadata.gz: b08186d2c9c95bc3933f15895e23031a09fd49af3d16886c82433831e5d395c4a496c9e58a2b22c42630d01b925d151beca01656446a3d10b0ca46acab83dcad
7
+ data.tar.gz: 9dcec07cc34048713d4ea627b1afac67c6885e82e8623ecad4b2f36af763c70c13e7d79a6d295c36cb2d502f62837b7df5f304c29d845f40d5be2b75660b64b7
@@ -0,0 +1,20 @@
1
+ # typed: true
2
+ module T
3
+ module Coerce
4
+ extend T::Sig
5
+ extend T::Generic
6
+
7
+ Elem = type_member
8
+
9
+ sig { params(args: T.untyped).returns(Elem) }
10
+ def from(args); end
11
+ end
12
+
13
+ module Private
14
+ module Types
15
+ class TypeAlias
16
+ def aliased_type; end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,175 @@
1
+ # typed: ignore
2
+ require 'sorbet-coerce'
3
+ require 'sorbet-runtime'
4
+
5
+ describe T::Coerce do
6
+ context 'when given T::Struct' do
7
+ class ParamInfo < T::Struct
8
+ const :name, String
9
+ const :lvl, T.nilable(Integer)
10
+ const :skill_ids, T::Array[Integer]
11
+ end
12
+
13
+ class ParamInfo2 < T::Struct
14
+ const :a, Integer
15
+ const :b, Integer
16
+ const :notes, T::Array[String]
17
+ end
18
+
19
+ class Param < T::Struct
20
+ const :id, Integer
21
+ const :role, String, default: 'wizard'
22
+ const :info, ParamInfo
23
+ const :opt, T.nilable(ParamInfo2)
24
+ end
25
+
26
+ class CustomType
27
+ attr_reader :a
28
+
29
+ def initialize(a)
30
+ @a = a
31
+ end
32
+ end
33
+
34
+ class UnsupportedCustomType
35
+ # Does not respond to new
36
+ end
37
+
38
+ let!(:param) {
39
+ T::Coerce[Param].new.from({
40
+ id: 1,
41
+ info: {
42
+ name: 'mango',
43
+ lvl: 100,
44
+ skill_ids: ['123', '456'],
45
+ },
46
+ opt: {
47
+ a: 1,
48
+ b: 2,
49
+ },
50
+ extra_attr: 'does not matter',
51
+ })
52
+ }
53
+
54
+ let!(:param2) {
55
+ T::Coerce[Param].new.from({
56
+ id: '2',
57
+ info: {
58
+ name: 'honeydew',
59
+ lvl: '5',
60
+ skill_ids: [],
61
+ },
62
+ opt: {
63
+ a: '1',
64
+ b: '2',
65
+ notes: [],
66
+ },
67
+ })
68
+ }
69
+
70
+ it 'reveals the right type' do
71
+ T.assert_type!(param, Param)
72
+ T.assert_type!(param.id, Integer)
73
+ T.assert_type!(param.info, ParamInfo)
74
+ T.assert_type!(param.info.name,String)
75
+ T.assert_type!(param.info.lvl, Integer)
76
+ T.assert_type!(param.opt, T.nilable(ParamInfo2))
77
+ end
78
+
79
+ it 'coerces correctly' do
80
+ expect(param.id).to eql 1
81
+ expect(param.role).to eql 'wizard'
82
+ expect(param.info.lvl).to eql 100
83
+ expect(param.info.name).to eql 'mango'
84
+ expect(param.info.skill_ids).to eql [123, 456]
85
+ expect(param.opt).to be nil # missing notes
86
+
87
+ expect(param2.id).to eql 2
88
+ expect(param2.info.name).to eql 'honeydew'
89
+ expect(param2.info.lvl).to eql 5
90
+ expect(param2.info.skill_ids).to eql []
91
+ expect(param2.opt.a).to eql 1
92
+ expect(param2.opt.b).to eql 2
93
+ expect(param2.opt.notes).to eql []
94
+
95
+ expect(
96
+ T::Coerce[T.nilable(Param)].new.from({
97
+ id: 3,
98
+ info: {
99
+ # missing required name
100
+ lvl: 2,
101
+ },
102
+ })
103
+ ).to be nil
104
+ end
105
+ end
106
+
107
+ context 'when the given T::Struct is invalid' do
108
+ class Param2 < T::Struct
109
+ const :id, Integer
110
+ const :info, T.any(Integer, String)
111
+ end
112
+
113
+ it 'raises an error' do
114
+ expect {
115
+ T::Coerce[Param2].new.from({id: 1, info: 1})
116
+ }.to raise_error(ArgumentError)
117
+ end
118
+ end
119
+
120
+ context 'when given primitive types' do
121
+ 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))
125
+ end
126
+
127
+ 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
132
+
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
135
+
136
+ expect(T::Coerce[T::Boolean].new.from('false')).to be false
137
+ expect(T::Coerce[T::Boolean].new.from('true')).to be true
138
+ end
139
+ end
140
+
141
+ context 'when given custom types' do
142
+ 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
145
+
146
+ expect{T::Coerce[UnsupportedCustomType].new.from(1)}.to raise_error(T::CoercionError)
147
+ end
148
+ end
149
+
150
+ context 'when dealing with arries' do
151
+ 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: [])
159
+ T.assert_type!(infos, T::Array[ParamInfo])
160
+ expect(infos.first.name).to eql 'a'
161
+
162
+ infos = T::Coerce[T::Array[ParamInfo]].new.from([{name: 'b', skill_ids: []}])
163
+ T.assert_type!(infos, T::Array[ParamInfo])
164
+ expect(infos.first.name).to eql 'b'
165
+ end
166
+ end
167
+
168
+ context 'when given a type alias' do
169
+ MyType = T.type_alias(T::Boolean)
170
+
171
+ it 'coerces correctly' do
172
+ expect(T::Coerce[MyType].new.from('false')).to be false
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,79 @@
1
+ # typed: false
2
+ require 'sorbet-coerce'
3
+ require 'sorbet-runtime'
4
+
5
+ describe T::Coerce do
6
+ context 'when given nested types' do
7
+ class User < T::Struct
8
+ const :id, Integer
9
+ const :valid, T.nilable(T::Boolean)
10
+ end
11
+
12
+ class NestedParam < T::Struct
13
+ const :users, T::Array[User]
14
+ const :params, T.nilable(NestedParam)
15
+ end
16
+
17
+ it 'works with nest T::Struct' do
18
+ converted = T::Coerce[NestedParam].new.from({
19
+ users: {id: '1'},
20
+ params: {
21
+ users: {id: '2', valid: 'true'},
22
+ params: {
23
+ users: {id: '3', valid: 'true'},
24
+ },
25
+ },
26
+ })
27
+ # => <NestedParam
28
+ # params=<NestedParam
29
+ # params=<NestedParam
30
+ # params=nil,
31
+ # users=[<User id=3 valid=true>]
32
+ # >,
33
+ # users=[<User id=2 valid=true>]
34
+ # >,
35
+ # users=[<User id=1 valid=nil>]
36
+ # >
37
+
38
+ expect(converted.users.map(&:id)).to eql([1])
39
+ expect(converted.params.users.map(&:id)).to eql([2])
40
+ expect(converted.params.params.users.map(&:id)).to eql([3])
41
+ end
42
+
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 eql [1, nil, 3]
47
+ 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']]),
52
+ ).to eql [[1], [2], [3]]
53
+
54
+ expect(T::Coerce[
55
+ T::Array[
56
+ T::Array[
57
+ T::Array[User]
58
+ ]
59
+ ]
60
+ ].new.from([[[{id: '1'}]]]).flatten.first.id).to eql(1)
61
+
62
+ expect(T::Coerce[
63
+ T::Array[
64
+ T::Array[
65
+ T::Array[
66
+ T::Array[
67
+ T::Array[T.nilable(User)]
68
+ ]
69
+ ]
70
+ ]
71
+ ]
72
+ ].new.from(nil).flatten).to eql([nil])
73
+
74
+ expect(T::Coerce[
75
+ T.nilable(T::Array[T.nilable(T::Array[T.nilable(User)])])
76
+ ].new.from([[{id: '1'}]]).flatten.map(&:id)).to eql([1])
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,14 @@
1
+ # typed: strict
2
+ require 'byebug'
3
+ require 'simplecov'
4
+
5
+ SimpleCov.start
6
+
7
+ if ENV['CI'] == 'true'
8
+ require 'codecov'
9
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
10
+ end
11
+
12
+ RSpec.configure do |config|
13
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
14
+ 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.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chan Zuckerberg Initiative
@@ -76,40 +76,40 @@ dependencies:
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:
@@ -120,6 +120,10 @@ extra_rdoc_files: []
120
120
  files:
121
121
  - lib/private/converter.rb
122
122
  - lib/sorbet-coerce.rb
123
+ - rbi/sorbet-coerce.rbi
124
+ - spec/coerce_spec.rb
125
+ - spec/nested_spec.rb
126
+ - spec/spec_helper.rb
123
127
  homepage: https://github.com/chanzuckerberg/sorbet-coerce
124
128
  licenses:
125
129
  - MIT
@@ -140,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
144
  version: '0'
141
145
  requirements: []
142
146
  rubyforge_project:
143
- rubygems_version: 2.7.9
147
+ rubygems_version: 2.7.7
144
148
  signing_key:
145
149
  specification_version: 4
146
150
  summary: A type coercion lib works with Sorbet's static type checker and type definitions;