knockapi 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8fe0e1d10c71e9d8525e72100489e37c46340b10b56774afdb51c91e42edd82
4
- data.tar.gz: '033928a1fd5f48acc9a83424c4c9f57a63fedd47510d3f6be6be822c6b0784e1'
3
+ metadata.gz: 926958a92b323486fd8828298e0a33c15c38291c3969dca3a695b313d7721fb4
4
+ data.tar.gz: f5865485cb4f021f109f68f31b5a975f846c2a59e535e11a4e70b88bb0487647
5
5
  SHA512:
6
- metadata.gz: 1d390284ec7a8d3d6229b24536c000a93c86864fcd8280ce8c9d9c5b68ee3ca6d18cb9ec2a8cf0c69609e839941cef90e34b5e2355a0b77caf152c3db1a983b2
7
- data.tar.gz: aa2d1c5854f1f2f747862ef533f4fa6867dc0379cfff4d7fb03b6d5197bc29ea4d634c595e26ab9c454c9da589217d4c84e037b26e019b9071df5a53ae73278e
6
+ metadata.gz: 5e0ac18c360382062090375543be29799a34b399f7626ed20a694a9f609a7ddf5323a917152babff04b9ec0b371d933319aa8837159226b986e845cd12c8df99
7
+ data.tar.gz: 8d3228da62dc6e4f04629604d9cfaf56f2918702f3c347b3589378d1c450424dc6772912469489cb480881236a68ba00674bc33aa5dde3dbbdf422bab7182417
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.1 (2025-06-18)
4
+
5
+ Full Changelog: [v1.8.0...v1.8.1](https://github.com/knocklabs/knock-ruby/compare/v1.8.0...v1.8.1)
6
+
7
+ ### Bug Fixes
8
+
9
+ * issue where we cannot mutate arrays on base model derivatives ([2c56679](https://github.com/knocklabs/knock-ruby/commit/2c56679d7f62da36dcae21fe5944d24211afe854))
10
+
11
+
12
+ ### Chores
13
+
14
+ * **ci:** enable for pull requests ([985de4e](https://github.com/knocklabs/knock-ruby/commit/985de4e91baf5684d7cf308f6e3de661fbb8163d))
15
+
16
+ ## 1.8.0 (2025-06-13)
17
+
18
+ Full Changelog: [v1.7.0...v1.8.0](https://github.com/knocklabs/knock-ruby/compare/v1.7.0...v1.8.0)
19
+
20
+ ### Features
21
+
22
+ * **api:** api update ([c69a4d1](https://github.com/knocklabs/knock-ruby/commit/c69a4d1cb01e0e8e7fb930b862ebeb06ae217bfc))
23
+
3
24
  ## 1.7.0 (2025-06-12)
4
25
 
5
26
  Full Changelog: [v1.6.0...v1.7.0](https://github.com/knocklabs/knock-ruby/compare/v1.6.0...v1.7.0)
data/README.md CHANGED
@@ -17,7 +17,7 @@ To use this gem, install via Bundler by adding the following to your application
17
17
  <!-- x-release-please-start-version -->
18
18
 
19
19
  ```ruby
20
- gem "knockapi", "~> 1.7.0"
20
+ gem "knockapi", "~> 1.8.1"
21
21
  ```
22
22
 
23
23
  <!-- x-release-please-end -->
@@ -9,6 +9,28 @@ module Knockapi
9
9
  end
10
10
 
11
11
  class ConversionError < Knockapi::Errors::Error
12
+ # @return [StandardError, nil]
13
+ def cause = @cause.nil? ? super : @cause
14
+
15
+ # @api private
16
+ #
17
+ # @param on [Class<StandardError>]
18
+ # @param method [Symbol]
19
+ # @param target [Object]
20
+ # @param value [Object]
21
+ # @param cause [StandardError, nil]
22
+ def initialize(on:, method:, target:, value:, cause: nil)
23
+ cls = on.name.split("::").last
24
+
25
+ message = [
26
+ "Failed to parse #{cls}.#{method} from #{value.class} to #{target.inspect}.",
27
+ "To get the unparsed API response, use #{cls}[#{method.inspect}].",
28
+ cause && "Cause: #{cause.message}"
29
+ ].filter(&:itself).join(" ")
30
+
31
+ @cause = cause
32
+ super(message)
33
+ end
12
34
  end
13
35
 
14
36
  class APIError < Knockapi::Errors::Error
@@ -62,10 +62,14 @@ module Knockapi
62
62
  #
63
63
  # @param state [Hash{Symbol=>Object}] .
64
64
  #
65
- # @option state [Boolean, :strong] :strictness
65
+ # @option state [Boolean] :translate_names
66
+ #
67
+ # @option state [Boolean] :strictness
66
68
  #
67
69
  # @option state [Hash{Symbol=>Object}] :exactness
68
70
  #
71
+ # @option state [Class<StandardError>] :error
72
+ #
69
73
  # @option state [Integer] :branched
70
74
  #
71
75
  # @return [Array<Object>, Object]
@@ -74,6 +78,7 @@ module Knockapi
74
78
 
75
79
  unless value.is_a?(Array)
76
80
  exactness[:no] += 1
81
+ state[:error] = TypeError.new("#{value.class} can't be coerced into #{Array}")
77
82
  return value
78
83
  end
79
84
 
@@ -60,7 +60,7 @@ module Knockapi
60
60
  [Knockapi::Internal::Type::Converter.type_info(type_info), type_info]
61
61
  end
62
62
 
63
- setter = "#{name_sym}="
63
+ setter = :"#{name_sym}="
64
64
  api_name = info.fetch(:api_name, name_sym)
65
65
  nilable = info.fetch(:nil?, false)
66
66
  const = if required && !nilable
@@ -84,30 +84,61 @@ module Knockapi
84
84
  type_fn: type_fn
85
85
  }
86
86
 
87
- define_method(setter) { @data.store(name_sym, _1) }
87
+ define_method(setter) do |value|
88
+ target = type_fn.call
89
+ state = Knockapi::Internal::Type::Converter.new_coerce_state(translate_names: false)
90
+ coerced = Knockapi::Internal::Type::Converter.coerce(target, value, state: state)
91
+ status = @coerced.store(name_sym, state.fetch(:error) || true)
92
+ stored =
93
+ case [target, status]
94
+ in [Knockapi::Internal::Type::Converter | Symbol, true]
95
+ coerced
96
+ else
97
+ value
98
+ end
99
+ @data.store(name_sym, stored)
100
+ end
88
101
 
102
+ # rubocop:disable Style/CaseEquality
103
+ # rubocop:disable Metrics/BlockLength
89
104
  define_method(name_sym) do
90
105
  target = type_fn.call
91
- value = @data.fetch(name_sym) { const == Knockapi::Internal::OMIT ? nil : const }
92
- state = {strictness: :strong, exactness: {yes: 0, no: 0, maybe: 0}, branched: 0}
93
- if (nilable || !required) && value.nil?
94
- nil
95
- else
96
- Knockapi::Internal::Type::Converter.coerce(
97
- target,
98
- value,
99
- state: state
106
+
107
+ case @coerced[name_sym]
108
+ in true | false if Knockapi::Internal::Type::Converter === target
109
+ @data.fetch(name_sym)
110
+ in ::StandardError => e
111
+ raise Knockapi::Errors::ConversionError.new(
112
+ on: self.class,
113
+ method: __method__,
114
+ target: target,
115
+ value: @data.fetch(name_sym),
116
+ cause: e
100
117
  )
118
+ else
119
+ Kernel.then do
120
+ value = @data.fetch(name_sym) { const == Knockapi::Internal::OMIT ? nil : const }
121
+ state = Knockapi::Internal::Type::Converter.new_coerce_state(translate_names: false)
122
+ if (nilable || !required) && value.nil?
123
+ nil
124
+ else
125
+ Knockapi::Internal::Type::Converter.coerce(
126
+ target, value, state: state
127
+ )
128
+ end
129
+ rescue StandardError => e
130
+ raise Knockapi::Errors::ConversionError.new(
131
+ on: self.class,
132
+ method: __method__,
133
+ target: target,
134
+ value: value,
135
+ cause: e
136
+ )
137
+ end
101
138
  end
102
- rescue StandardError => e
103
- cls = self.class.name.split("::").last
104
- message = [
105
- "Failed to parse #{cls}.#{__method__} from #{value.class} to #{target.inspect}.",
106
- "To get the unparsed API response, use #{cls}[#{__method__.inspect}].",
107
- "Cause: #{e.message}"
108
- ].join(" ")
109
- raise Knockapi::Errors::ConversionError.new(message)
110
139
  end
140
+ # rubocop:enable Metrics/BlockLength
141
+ # rubocop:enable Style/CaseEquality
111
142
  end
112
143
 
113
144
  # @api private
@@ -207,23 +238,28 @@ module Knockapi
207
238
  #
208
239
  # @param state [Hash{Symbol=>Object}] .
209
240
  #
210
- # @option state [Boolean, :strong] :strictness
241
+ # @option state [Boolean] :translate_names
242
+ #
243
+ # @option state [Boolean] :strictness
211
244
  #
212
245
  # @option state [Hash{Symbol=>Object}] :exactness
213
246
  #
247
+ # @option state [Class<StandardError>] :error
248
+ #
214
249
  # @option state [Integer] :branched
215
250
  #
216
251
  # @return [self, Object]
217
252
  def coerce(value, state:)
218
253
  exactness = state.fetch(:exactness)
219
254
 
220
- if value.is_a?(self.class)
255
+ if value.is_a?(self)
221
256
  exactness[:yes] += 1
222
257
  return value
223
258
  end
224
259
 
225
260
  unless (val = Knockapi::Internal::Util.coerce_hash(value)).is_a?(Hash)
226
261
  exactness[:no] += 1
262
+ state[:error] = TypeError.new("#{value.class} can't be coerced into #{Hash}")
227
263
  return value
228
264
  end
229
265
  exactness[:yes] += 1
@@ -231,13 +267,15 @@ module Knockapi
231
267
  keys = val.keys.to_set
232
268
  instance = new
233
269
  data = instance.to_h
270
+ status = instance.instance_variable_get(:@coerced)
234
271
 
235
272
  # rubocop:disable Metrics/BlockLength
236
273
  fields.each do |name, field|
237
274
  mode, required, target = field.fetch_values(:mode, :required, :type)
238
275
  api_name, nilable, const = field.fetch_values(:api_name, :nilable, :const)
276
+ src_name = state.fetch(:translate_names) ? api_name : name
239
277
 
240
- unless val.key?(api_name)
278
+ unless val.key?(src_name)
241
279
  if required && mode != :dump && const == Knockapi::Internal::OMIT
242
280
  exactness[nilable ? :maybe : :no] += 1
243
281
  else
@@ -246,9 +284,10 @@ module Knockapi
246
284
  next
247
285
  end
248
286
 
249
- item = val.fetch(api_name)
250
- keys.delete(api_name)
287
+ item = val.fetch(src_name)
288
+ keys.delete(src_name)
251
289
 
290
+ state[:error] = nil
252
291
  converted =
253
292
  if item.nil? && (nilable || !required)
254
293
  exactness[nilable ? :yes : :maybe] += 1
@@ -262,6 +301,8 @@ module Knockapi
262
301
  item
263
302
  end
264
303
  end
304
+
305
+ status.store(name, state.fetch(:error) || true)
265
306
  data.store(name, converted)
266
307
  end
267
308
  # rubocop:enable Metrics/BlockLength
@@ -437,7 +478,18 @@ module Knockapi
437
478
  # Create a new instance of a model.
438
479
  #
439
480
  # @param data [Hash{Symbol=>Object}, self]
440
- def initialize(data = {}) = (@data = Knockapi::Internal::Util.coerce_hash!(data).to_h)
481
+ def initialize(data = {})
482
+ @data = {}
483
+ @coerced = {}
484
+ Knockapi::Internal::Util.coerce_hash!(data).each do
485
+ if self.class.known_fields.key?(_1)
486
+ public_send(:"#{_1}=", _2)
487
+ else
488
+ @data.store(_1, _2)
489
+ @coerced.store(_1, false)
490
+ end
491
+ end
492
+ end
441
493
 
442
494
  class << self
443
495
  # @api private
@@ -31,14 +31,20 @@ module Knockapi
31
31
  class << self
32
32
  # @api private
33
33
  #
34
+ # Coerce value to Boolean if possible, otherwise return the original value.
35
+ #
34
36
  # @param value [Boolean, Object]
35
37
  #
36
38
  # @param state [Hash{Symbol=>Object}] .
37
39
  #
38
- # @option state [Boolean, :strong] :strictness
40
+ # @option state [Boolean] :translate_names
41
+ #
42
+ # @option state [Boolean] :strictness
39
43
  #
40
44
  # @option state [Hash{Symbol=>Object}] :exactness
41
45
  #
46
+ # @option state [Class<StandardError>] :error
47
+ #
42
48
  # @option state [Integer] :branched
43
49
  #
44
50
  # @return [Boolean, Object]
@@ -15,10 +15,14 @@ module Knockapi
15
15
  #
16
16
  # @param state [Hash{Symbol=>Object}] .
17
17
  #
18
- # @option state [Boolean, :strong] :strictness
18
+ # @option state [Boolean] :translate_names
19
+ #
20
+ # @option state [Boolean] :strictness
19
21
  #
20
22
  # @option state [Hash{Symbol=>Object}] :exactness
21
23
  #
24
+ # @option state [Class<StandardError>] :error
25
+ #
22
26
  # @option state [Integer] :branched
23
27
  #
24
28
  # @return [Object]
@@ -94,6 +98,21 @@ module Knockapi
94
98
  end
95
99
  end
96
100
 
101
+ # @api private
102
+ #
103
+ # @param translate_names [Boolean]
104
+ #
105
+ # @return [Hash{Symbol=>Object}]
106
+ def new_coerce_state(translate_names: true)
107
+ {
108
+ translate_names: translate_names,
109
+ strictness: true,
110
+ exactness: {yes: 0, no: 0, maybe: 0},
111
+ error: nil,
112
+ branched: 0
113
+ }
114
+ end
115
+
97
116
  # @api private
98
117
  #
99
118
  # Based on `target`, transform `value` into `target`, to the extent possible:
@@ -110,14 +129,11 @@ module Knockapi
110
129
  #
111
130
  # @param value [Object]
112
131
  #
113
- # @param state [Hash{Symbol=>Object}] The `strictness` is one of `true`, `false`, or `:strong`. This informs the
114
- # coercion strategy when we have to decide between multiple possible conversion
115
- # targets:
132
+ # @param state [Hash{Symbol=>Object}] The `strictness` is one of `true`, `false`. This informs the coercion strategy
133
+ # when we have to decide between multiple possible conversion targets:
116
134
  #
117
135
  # - `true`: the conversion must be exact, with minimum coercion.
118
136
  # - `false`: the conversion can be approximate, with some coercion.
119
- # - `:strong`: the conversion must be exact, with no coercion, and raise an error
120
- # if not possible.
121
137
  #
122
138
  # The `exactness` is `Hash` with keys being one of `yes`, `no`, or `maybe`. For
123
139
  # any given conversion attempt, the exactness will be updated based on how closely
@@ -130,21 +146,20 @@ module Knockapi
130
146
  #
131
147
  # See implementation below for more details.
132
148
  #
133
- # @option state [Boolean, :strong] :strictness
149
+ # @option state [Boolean] :translate_names
150
+ #
151
+ # @option state [Boolean] :strictness
134
152
  #
135
153
  # @option state [Hash{Symbol=>Object}] :exactness
136
154
  #
155
+ # @option state [Class<StandardError>] :error
156
+ #
137
157
  # @option state [Integer] :branched
138
158
  #
139
159
  # @return [Object]
140
- def coerce(
141
- target,
142
- value,
143
- state: {strictness: true, exactness: {yes: 0, no: 0, maybe: 0}, branched: 0}
144
- )
145
- # rubocop:disable Lint/SuppressedException
160
+ def coerce(target, value, state: Knockapi::Internal::Type::Converter.new_coerce_state)
146
161
  # rubocop:disable Metrics/BlockNesting
147
- strictness, exactness = state.fetch_values(:strictness, :exactness)
162
+ exactness = state.fetch(:exactness)
148
163
 
149
164
  case target
150
165
  in Knockapi::Internal::Type::Converter
@@ -160,29 +175,26 @@ module Knockapi
160
175
  exactness[value.nil? ? :yes : :maybe] += 1
161
176
  return nil
162
177
  in -> { _1 <= Integer }
163
- if value.is_a?(Integer)
178
+ case value
179
+ in Integer
164
180
  exactness[:yes] += 1
165
181
  return value
166
- elsif strictness == :strong && Integer(value, exception: false) != value
167
- message = "no implicit conversion of #{value.class} into #{target.inspect}"
168
- raise value.is_a?(Numeric) ? ArgumentError.new(message) : TypeError.new(message)
169
182
  else
170
183
  Kernel.then do
171
184
  return Integer(value).tap { exactness[:maybe] += 1 }
172
- rescue ArgumentError, TypeError
185
+ rescue ArgumentError, TypeError => e
186
+ state[:error] = e
173
187
  end
174
188
  end
175
189
  in -> { _1 <= Float }
176
190
  if value.is_a?(Numeric)
177
191
  exactness[:yes] += 1
178
192
  return Float(value)
179
- elsif strictness == :strong
180
- message = "no implicit conversion of #{value.class} into #{target.inspect}"
181
- raise TypeError.new(message)
182
193
  else
183
194
  Kernel.then do
184
195
  return Float(value).tap { exactness[:maybe] += 1 }
185
- rescue ArgumentError, TypeError
196
+ rescue ArgumentError, TypeError => e
197
+ state[:error] = e
186
198
  end
187
199
  end
188
200
  in -> { _1 <= String }
@@ -194,16 +206,13 @@ module Knockapi
194
206
  exactness[:yes] += 1
195
207
  return value.string
196
208
  else
197
- if strictness == :strong
198
- message = "no implicit conversion of #{value.class} into #{target.inspect}"
199
- raise TypeError.new(message)
200
- end
209
+ state[:error] = TypeError.new("#{value.class} can't be coerced into #{String}")
201
210
  end
202
211
  in -> { _1 <= Date || _1 <= Time }
203
212
  Kernel.then do
204
213
  return target.parse(value).tap { exactness[:yes] += 1 }
205
214
  rescue ArgumentError, TypeError => e
206
- raise e if strictness == :strong
215
+ state[:error] = e
207
216
  end
208
217
  in -> { _1 <= StringIO } if value.is_a?(String)
209
218
  exactness[:yes] += 1
@@ -221,10 +230,8 @@ module Knockapi
221
230
  return value
222
231
  end
223
232
  else
224
- if strictness == :strong
225
- message = "cannot convert non-matching #{value.class} into #{target.inspect}"
226
- raise ArgumentError.new(message)
227
- end
233
+ message = "cannot convert non-matching #{value.class} into #{target.inspect}"
234
+ state[:error] = ArgumentError.new(message)
228
235
  end
229
236
  else
230
237
  end
@@ -232,7 +239,6 @@ module Knockapi
232
239
  exactness[:no] += 1
233
240
  value
234
241
  # rubocop:enable Metrics/BlockNesting
235
- # rubocop:enable Lint/SuppressedException
236
242
  end
237
243
 
238
244
  # @api private
@@ -277,8 +283,10 @@ module Knockapi
277
283
  define_sorbet_constant!(:CoerceState) do
278
284
  T.type_alias do
279
285
  {
280
- strictness: T.any(T::Boolean, Symbol),
286
+ translate_names: T::Boolean,
287
+ strictness: T::Boolean,
281
288
  exactness: {yes: Integer, no: Integer, maybe: Integer},
289
+ error: T::Class[StandardError],
282
290
  branched: Integer
283
291
  }
284
292
  end
@@ -56,10 +56,14 @@ module Knockapi
56
56
  #
57
57
  # @param state [Hash{Symbol=>Object}] .
58
58
  #
59
- # @option state [Boolean, :strong] :strictness
59
+ # @option state [Boolean] :translate_names
60
+ #
61
+ # @option state [Boolean] :strictness
60
62
  #
61
63
  # @option state [Hash{Symbol=>Object}] :exactness
62
64
  #
65
+ # @option state [Class<StandardError>] :error
66
+ #
63
67
  # @option state [Integer] :branched
64
68
  #
65
69
  # @return [Symbol, Object]
@@ -70,8 +74,12 @@ module Knockapi
70
74
  if values.include?(val)
71
75
  exactness[:yes] += 1
72
76
  val
77
+ elsif values.first&.class == val.class
78
+ exactness[:maybe] += 1
79
+ value
73
80
  else
74
- exactness[values.first&.class == val.class ? :maybe : :no] += 1
81
+ exactness[:no] += 1
82
+ state[:error] = TypeError.new("#{value.class} can't be coerced into #{self}")
75
83
  value
76
84
  end
77
85
  end
@@ -45,10 +45,14 @@ module Knockapi
45
45
  #
46
46
  # @param state [Hash{Symbol=>Object}] .
47
47
  #
48
- # @option state [Boolean, :strong] :strictness
48
+ # @option state [Boolean] :translate_names
49
+ #
50
+ # @option state [Boolean] :strictness
49
51
  #
50
52
  # @option state [Hash{Symbol=>Object}] :exactness
51
53
  #
54
+ # @option state [Class<StandardError>] :error
55
+ #
52
56
  # @option state [Integer] :branched
53
57
  #
54
58
  # @return [StringIO, Object]
@@ -62,6 +66,7 @@ module Knockapi
62
66
  exactness[:yes] += 1
63
67
  value
64
68
  else
69
+ state[:error] = TypeError.new("#{value.class} can't be coerced into #{StringIO}")
65
70
  exactness[:no] += 1
66
71
  value
67
72
  end
@@ -77,10 +77,14 @@ module Knockapi
77
77
  #
78
78
  # @param state [Hash{Symbol=>Object}] .
79
79
  #
80
- # @option state [Boolean, :strong] :strictness
80
+ # @option state [Boolean] :translate_names
81
+ #
82
+ # @option state [Boolean] :strictness
81
83
  #
82
84
  # @option state [Hash{Symbol=>Object}] :exactness
83
85
  #
86
+ # @option state [Class<StandardError>] :error
87
+ #
84
88
  # @option state [Integer] :branched
85
89
  #
86
90
  # @return [Hash{Symbol=>Object}, Object]
@@ -89,6 +93,7 @@ module Knockapi
89
93
 
90
94
  unless value.is_a?(Hash)
91
95
  exactness[:no] += 1
96
+ state[:error] = TypeError.new("#{value.class} can't be coerced into #{Hash}")
92
97
  return value
93
98
  end
94
99
 
@@ -126,14 +126,23 @@ module Knockapi
126
126
 
127
127
  # @api private
128
128
  #
129
+ # Tries to efficiently coerce the given value to one of the known variants.
130
+ #
131
+ # If the value cannot match any of the known variants, the coercion is considered
132
+ # non-viable and returns the original value.
133
+ #
129
134
  # @param value [Object]
130
135
  #
131
136
  # @param state [Hash{Symbol=>Object}] .
132
137
  #
133
- # @option state [Boolean, :strong] :strictness
138
+ # @option state [Boolean] :translate_names
139
+ #
140
+ # @option state [Boolean] :strictness
134
141
  #
135
142
  # @option state [Hash{Symbol=>Object}] :exactness
136
143
  #
144
+ # @option state [Class<StandardError>] :error
145
+ #
137
146
  # @option state [Integer] :branched
138
147
  #
139
148
  # @return [Object]
@@ -144,7 +153,6 @@ module Knockapi
144
153
 
145
154
  strictness = state.fetch(:strictness)
146
155
  exactness = state.fetch(:exactness)
147
- state[:strictness] = strictness == :strong ? true : strictness
148
156
 
149
157
  alternatives = []
150
158
  known_variants.each do |_, variant_fn|
@@ -163,13 +171,10 @@ module Knockapi
163
171
  end
164
172
  end
165
173
 
166
- case alternatives.sort_by(&:first)
174
+ case alternatives.sort_by!(&:first)
167
175
  in []
168
176
  exactness[:no] += 1
169
- if strictness == :strong
170
- message = "no possible conversion of #{value.class} into a variant of #{target.inspect}"
171
- raise ArgumentError.new(message)
172
- end
177
+ state[:error] = ArgumentError.new("no matching variant for #{value.inspect}")
173
178
  value
174
179
  in [[_, exact, coerced], *]
175
180
  exact.each { exactness[_1] += _2 }
@@ -33,14 +33,20 @@ module Knockapi
33
33
  class << self
34
34
  # @api private
35
35
  #
36
+ # No coercion needed for Unknown type.
37
+ #
36
38
  # @param value [Object]
37
39
  #
38
40
  # @param state [Hash{Symbol=>Object}] .
39
41
  #
40
- # @option state [Boolean, :strong] :strictness
42
+ # @option state [Boolean] :translate_names
43
+ #
44
+ # @option state [Boolean] :strictness
41
45
  #
42
46
  # @option state [Hash{Symbol=>Object}] :exactness
43
47
  #
48
+ # @option state [Class<StandardError>] :error
49
+ #
44
50
  # @option state [Integer] :branched
45
51
  #
46
52
  # @return [Object]
@@ -36,13 +36,14 @@ module Knockapi
36
36
  optional :cursor, String
37
37
 
38
38
  # @!attribute exclude_archived
39
- # Set to true to exclude archived channels from the list.
39
+ # Set to true to exclude archived channels from the list. Defaults to `true` when
40
+ # not explicitly provided.
40
41
  #
41
42
  # @return [Boolean, nil]
42
43
  optional :exclude_archived, Knockapi::Internal::Type::Boolean
43
44
 
44
45
  # @!attribute limit
45
- # The maximum number of channels to return.
46
+ # The maximum number of channels to return. Defaults to 200.
46
47
  #
47
48
  # @return [Integer, nil]
48
49
  optional :limit, Integer
@@ -55,7 +56,8 @@ module Knockapi
55
56
 
56
57
  # @!attribute types
57
58
  # Mix and match channel types by providing a comma-separated list of any
58
- # combination of public_channel, private_channel, mpim, im.
59
+ # combination of public_channel, private_channel, mpim, im. Defaults to
60
+ # `"public_channel,private_channel"`.
59
61
  #
60
62
  # @return [String, nil]
61
63
  optional :types, String
@@ -67,9 +69,9 @@ module Knockapi
67
69
  #
68
70
  # @param cursor [String] Paginate through collections of data by setting the cursor parameter to a next_c
69
71
  #
70
- # @param exclude_archived [Boolean] Set to true to exclude archived channels from the list.
72
+ # @param exclude_archived [Boolean] Set to true to exclude archived channels from the list. Defaults to `true` when
71
73
  #
72
- # @param limit [Integer] The maximum number of channels to return.
74
+ # @param limit [Integer] The maximum number of channels to return. Defaults to 200.
73
75
  #
74
76
  # @param team_id [String] Encoded team ID (T1234) to list channels in, required if org token is used.
75
77
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Knockapi
4
- VERSION = "1.7.0"
4
+ VERSION = "1.8.1"
5
5
  end
@@ -8,6 +8,22 @@ module Knockapi
8
8
  end
9
9
 
10
10
  class ConversionError < Knockapi::Errors::Error
11
+ sig { returns(T.nilable(StandardError)) }
12
+ def cause
13
+ end
14
+
15
+ # @api private
16
+ sig do
17
+ params(
18
+ on: T::Class[StandardError],
19
+ method: Symbol,
20
+ target: T.anything,
21
+ value: T.anything,
22
+ cause: T.nilable(StandardError)
23
+ ).returns(T.attached_class)
24
+ end
25
+ def self.new(on:, method:, target:, value:, cause: nil)
26
+ end
11
27
  end
12
28
 
13
29
  class APIError < Knockapi::Errors::Error
@@ -22,6 +22,8 @@ module Knockapi
22
22
 
23
23
  class << self
24
24
  # @api private
25
+ #
26
+ # Coerce value to Boolean if possible, otherwise return the original value.
25
27
  sig do
26
28
  override
27
29
  .params(
@@ -15,12 +15,14 @@ module Knockapi
15
15
  CoerceState =
16
16
  T.type_alias do
17
17
  {
18
- strictness: T.any(T::Boolean, Symbol),
18
+ translate_names: T::Boolean,
19
+ strictness: T::Boolean,
19
20
  exactness: {
20
21
  yes: Integer,
21
22
  no: Integer,
22
23
  maybe: Integer
23
24
  },
25
+ error: T::Class[StandardError],
24
26
  branched: Integer
25
27
  }
26
28
  end
@@ -88,6 +90,15 @@ module Knockapi
88
90
  def self.type_info(spec)
89
91
  end
90
92
 
93
+ # @api private
94
+ sig do
95
+ params(translate_names: T::Boolean).returns(
96
+ Knockapi::Internal::Type::Converter::CoerceState
97
+ )
98
+ end
99
+ def self.new_coerce_state(translate_names: true)
100
+ end
101
+
91
102
  # @api private
92
103
  #
93
104
  # Based on `target`, transform `value` into `target`, to the extent possible:
@@ -109,14 +120,11 @@ module Knockapi
109
120
  def self.coerce(
110
121
  target,
111
122
  value,
112
- # The `strictness` is one of `true`, `false`, or `:strong`. This informs the
113
- # coercion strategy when we have to decide between multiple possible conversion
114
- # targets:
123
+ # The `strictness` is one of `true`, `false`. This informs the coercion strategy
124
+ # when we have to decide between multiple possible conversion targets:
115
125
  #
116
126
  # - `true`: the conversion must be exact, with minimum coercion.
117
127
  # - `false`: the conversion can be approximate, with some coercion.
118
- # - `:strong`: the conversion must be exact, with no coercion, and raise an error
119
- # if not possible.
120
128
  #
121
129
  # The `exactness` is `Hash` with keys being one of `yes`, `no`, or `maybe`. For
122
130
  # any given conversion attempt, the exactness will be updated based on how closely
@@ -128,15 +136,7 @@ module Knockapi
128
136
  # - `no`: the value cannot be converted to the target type.
129
137
  #
130
138
  # See implementation below for more details.
131
- state: {
132
- strictness: true,
133
- exactness: {
134
- yes: 0,
135
- no: 0,
136
- maybe: 0
137
- },
138
- branched: 0
139
- }
139
+ state: Knockapi::Internal::Type::Converter.new_coerce_state
140
140
  )
141
141
  end
142
142
 
@@ -78,6 +78,11 @@ module Knockapi
78
78
  end
79
79
 
80
80
  # @api private
81
+ #
82
+ # Tries to efficiently coerce the given value to one of the known variants.
83
+ #
84
+ # If the value cannot match any of the known variants, the coercion is considered
85
+ # non-viable and returns the original value.
81
86
  sig do
82
87
  override
83
88
  .params(
@@ -22,6 +22,8 @@ module Knockapi
22
22
 
23
23
  class << self
24
24
  # @api private
25
+ #
26
+ # No coercion needed for Unknown type.
25
27
  sig do
26
28
  override
27
29
  .params(
@@ -83,14 +83,15 @@ module Knockapi
83
83
  sig { params(cursor: String).void }
84
84
  attr_writer :cursor
85
85
 
86
- # Set to true to exclude archived channels from the list.
86
+ # Set to true to exclude archived channels from the list. Defaults to `true` when
87
+ # not explicitly provided.
87
88
  sig { returns(T.nilable(T::Boolean)) }
88
89
  attr_reader :exclude_archived
89
90
 
90
91
  sig { params(exclude_archived: T::Boolean).void }
91
92
  attr_writer :exclude_archived
92
93
 
93
- # The maximum number of channels to return.
94
+ # The maximum number of channels to return. Defaults to 200.
94
95
  sig { returns(T.nilable(Integer)) }
95
96
  attr_reader :limit
96
97
 
@@ -105,7 +106,8 @@ module Knockapi
105
106
  attr_writer :team_id
106
107
 
107
108
  # Mix and match channel types by providing a comma-separated list of any
108
- # combination of public_channel, private_channel, mpim, im.
109
+ # combination of public_channel, private_channel, mpim, im. Defaults to
110
+ # `"public_channel,private_channel"`.
109
111
  sig { returns(T.nilable(String)) }
110
112
  attr_reader :types
111
113
 
@@ -126,14 +128,16 @@ module Knockapi
126
128
  # next_cursor attribute returned by a previous request's response_metadata.
127
129
  # Default value fetches the first "page" of the collection.
128
130
  cursor: nil,
129
- # Set to true to exclude archived channels from the list.
131
+ # Set to true to exclude archived channels from the list. Defaults to `true` when
132
+ # not explicitly provided.
130
133
  exclude_archived: nil,
131
- # The maximum number of channels to return.
134
+ # The maximum number of channels to return. Defaults to 200.
132
135
  limit: nil,
133
136
  # Encoded team ID (T1234) to list channels in, required if org token is used.
134
137
  team_id: nil,
135
138
  # Mix and match channel types by providing a comma-separated list of any
136
- # combination of public_channel, private_channel, mpim, im.
139
+ # combination of public_channel, private_channel, mpim, im. Defaults to
140
+ # `"public_channel,private_channel"`.
137
141
  types: nil
138
142
  )
139
143
  end
@@ -5,6 +5,15 @@ module Knockapi
5
5
  end
6
6
 
7
7
  class ConversionError < Knockapi::Errors::Error
8
+ def cause: -> StandardError?
9
+
10
+ def initialize: (
11
+ on: Class,
12
+ method: Symbol,
13
+ target: top,
14
+ value: top,
15
+ ?cause: StandardError?
16
+ ) -> void
8
17
  end
9
18
 
10
19
  class APIError < Knockapi::Errors::Error
@@ -8,8 +8,10 @@ module Knockapi
8
8
 
9
9
  type coerce_state =
10
10
  {
11
- strictness: bool | :strong,
11
+ translate_names: bool,
12
+ strictness: bool,
12
13
  exactness: { yes: Integer, no: Integer, maybe: Integer },
14
+ error: Class,
13
15
  branched: Integer
14
16
  }
15
17
 
@@ -37,6 +39,10 @@ module Knockapi
37
39
  | Knockapi::Internal::Type::Converter::input spec
38
40
  ) -> (^-> top)
39
41
 
42
+ def self.new_coerce_state: (
43
+ ?translate_names: bool
44
+ ) -> Knockapi::Internal::Type::Converter::coerce_state
45
+
40
46
  def self.coerce: (
41
47
  Knockapi::Internal::Type::Converter::input target,
42
48
  top value,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knockapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Knock
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-12 00:00:00.000000000 Z
11
+ date: 2025-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool