gapic-common 1.2.0 → 1.4.0

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: ac5c845df865497acbdcb4656a47667dd657083d85ef964f633fc0540ef80493
4
- data.tar.gz: 851ab12cf7a9e8d5e5f93c991846f0a4701a5a96047fef0ebb17392a55061984
3
+ metadata.gz: 6dbc3fc8f2b53d01d228f377851d21d941cedd307a9528e7ba732c5e8d2565e2
4
+ data.tar.gz: d6ded505cf91783149b49373f2efb2ef105860705a22f2940c27f6b27d83be39
5
5
  SHA512:
6
- metadata.gz: 3225a82b3d2b7006fa0191c55e46e836724ec07d240d7fc5eb11188ccc5613572a714a012a5e904d85cc70d13bb816cd735faf0c3b458e54552e36f483f07f21
7
- data.tar.gz: 7d4e26754019a71ae7bca52e2e195b71db46c755c6eca1cce34504990e31f2055d390119d9ae0bf81d1b217f7f1d3185200557da3c4d04b8aa251a3d87a13b54
6
+ metadata.gz: e7533c44cae4643cec30b87e96fc61750ca2ba2a3458c711788d51987436b25ca3248f6c8d011cf06a00992eec4458e3403ac0b64850a43d153368cd3a6c0203
7
+ data.tar.gz: cd51bd91e418f8d5c71c3f6e484b29f4c420121b14baca686309f074928162f97c0259555bd32d28cb6e747a397c99c0e3d5aeb6cd99085d309ed61482af7cfa
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Release History
2
2
 
3
+ ### 1.4.0 (2026-09-23)
4
+
5
+ #### Features
6
+
7
+ * add custom predicate matching to RetryPolicy ([#59](https://github.com/googleapis/ruby-core-libraries/issues/59))
8
+ #### Bug Fixes
9
+
10
+ * require grpc >= 1.83 for post-quantum key exchange ([#73](https://github.com/googleapis/ruby-core-libraries/issues/73))
11
+ * only use emit_default for proto messages ([#75](https://github.com/googleapis/ruby-core-libraries/issues/75))
12
+
13
+ ### 1.3.0 (2026-03-05)
14
+
15
+ #### Features
16
+
17
+ * add jitter to retries ([#54](https://github.com/googleapis/ruby-core-libraries/issues/54))
18
+
3
19
  ### 1.2.0 (2025-09-04)
4
20
 
5
21
  #### Features
data/README.md CHANGED
@@ -15,7 +15,7 @@ convenient and idiomatic API surface to callers.
15
15
 
16
16
  ## Supported Ruby Versions
17
17
 
18
- This library is supported on Ruby 3.0+.
18
+ This library is supported on Ruby 3.2+.
19
19
 
20
20
  Google provides official support for Ruby versions that are actively supported
21
21
  by Ruby Core—that is, Ruby versions that are either in normal maintenance or in
@@ -24,6 +24,35 @@ work, but are unsupported and not recommended. See
24
24
  https://www.ruby-lang.org/en/downloads/branches/ for details about the Ruby
25
25
  support schedule.
26
26
 
27
+ ## Post-Quantum Key Exchange
28
+
29
+ Clients built on this library negotiate the hybrid post-quantum key exchange
30
+ group `X25519MLKEM768` — classical X25519 paired with ML-KEM-768 ([NIST FIPS
31
+ 203][]) — over TLS 1.3. No application code changes are required, because the
32
+ handshake belongs to the transport layer. The two transports source that
33
+ support differently:
34
+
35
+ | Transport | Cryptographic provider | Requirement | Enforced by this gem? |
36
+ | --- | --- | --- | --- |
37
+ | gRPC | BoringSSL, vendored inside the `grpc` gem | `grpc >= 1.83` | Yes |
38
+ | REST | Host system OpenSSL, via `Net::HTTP` and Faraday | OpenSSL `>= 3.5` | No |
39
+
40
+ gRPC clients get post-quantum key exchange automatically: `gapic-common`
41
+ depends on `grpc >= 1.83`, the first release to offer `X25519MLKEM768` in the
42
+ TLS ClientHello by default.
43
+
44
+ The REST requirement cannot be expressed as a gem dependency. Ruby's `openssl`
45
+ is a default gem bound to whatever `libssl` the host provides, and ML-KEM first
46
+ ships in OpenSSL 3.5. On an older host, REST connections negotiate classical
47
+ X25519 instead — a safe fallback rather than an error, but not post-quantum. To
48
+ check the OpenSSL your Ruby is linked against:
49
+
50
+ ```sh
51
+ ruby -ropenssl -e 'puts OpenSSL::OPENSSL_LIBRARY_VERSION'
52
+ ```
53
+
54
+ [NIST FIPS 203]: https://csrc.nist.gov/pubs/fips/203/final
55
+
27
56
  ## Contributing
28
57
 
29
58
  Contributions to this library are always welcome and highly encouraged.
@@ -30,8 +30,9 @@ module Gapic
30
30
  # @param initial_delay [Numeric] Initial delay in seconds.
31
31
  # @param multiplier [Numeric] The delay scaling factor for each subsequent retry attempt.
32
32
  # @param max_delay [Numeric] Maximum delay in seconds.
33
+ # @param jitter [Numeric] Random jitter added to the delay in seconds.
33
34
  #
34
- def initialize retry_codes: nil, initial_delay: nil, multiplier: nil, max_delay: nil
35
+ def initialize retry_codes: nil, initial_delay: nil, multiplier: nil, max_delay: nil, jitter: nil
35
36
  super
36
37
  end
37
38
  end
@@ -31,6 +31,11 @@ module Gapic
31
31
  # @return [Numeric] Default timeout threshold value in seconds.
32
32
  DEFAULT_TIMEOUT = 3600 # One hour
33
33
 
34
+ # @private
35
+ # @return [Numeric] Default random jitter added to delay in seconds.
36
+ DEFAULT_JITTER = 1.0
37
+ private_constant :DEFAULT_JITTER
38
+
34
39
  ##
35
40
  # Create new Gapic::Common::RetryPolicy instance.
36
41
  #
@@ -39,14 +44,27 @@ module Gapic
39
44
  # @param multiplier [Numeric] The delay scaling factor for each subsequent retry attempt.
40
45
  # @param retry_codes [Array<String|Integer>] List of retry codes.
41
46
  # @param timeout [Numeric] Timeout threshold value in seconds.
47
+ # @param jitter [Numeric] Random jitter added to the delay in seconds.
48
+ # @param retry_predicate [Proc, nil] The predicate to evaluate whether to retry on a given error. Optional.
49
+ # If the predicate is specified, it is run first. If it returns nil, the decision on
50
+ # whether to retry is made on the basis of retry_codes. Otherwise, the truthiness of
51
+ # the return value determines whether to retry.
42
52
  #
43
- def initialize initial_delay: nil, max_delay: nil, multiplier: nil, retry_codes: nil, timeout: nil
53
+ def initialize initial_delay: nil, max_delay: nil, multiplier: nil, retry_codes: nil, timeout: nil,
54
+ jitter: nil, retry_predicate: nil
55
+ raise ArgumentError, "jitter cannot be negative" if jitter&.negative?
56
+ if retry_predicate && !retry_predicate.respond_to?(:call)
57
+ raise ArgumentError, "retry_predicate must respond to :call"
58
+ end
59
+
44
60
  # Instance values are set as `nil` to determine whether values are overriden from default.
45
61
  @initial_delay = initial_delay
46
62
  @max_delay = max_delay
47
63
  @multiplier = multiplier
48
64
  @retry_codes = convert_codes retry_codes
49
65
  @timeout = timeout
66
+ @jitter = jitter
67
+ @retry_predicate = retry_predicate
50
68
  start!
51
69
  end
52
70
 
@@ -75,6 +93,24 @@ module Gapic
75
93
  @timeout || DEFAULT_TIMEOUT
76
94
  end
77
95
 
96
+ # @return [Numeric] Random jitter added to the delay in seconds.
97
+ def jitter
98
+ @jitter || DEFAULT_JITTER
99
+ end
100
+
101
+ ##
102
+ # The predicate to evaluate whether to retry on a given error. Optional.
103
+ #
104
+ # When a predicate is specified:
105
+ # 1. The predicate is executed first on the error.
106
+ # 2. If it returns nil, the decision on whether to retry is made on the basis of retry_codes.
107
+ # 3. Otherwise, the truthiness of the return value determines whether to retry.
108
+ #
109
+ # @return [Proc, nil]
110
+ def retry_predicate
111
+ @retry_predicate
112
+ end
113
+
78
114
  ##
79
115
  # Returns a duplicate in a non-executing state, i.e. with the deadline
80
116
  # and current delay reset.
@@ -86,7 +122,9 @@ module Gapic
86
122
  max_delay: @max_delay,
87
123
  multiplier: @multiplier,
88
124
  retry_codes: @retry_codes,
89
- timeout: @timeout
125
+ timeout: @timeout,
126
+ jitter: @jitter,
127
+ retry_predicate: @retry_predicate
90
128
  end
91
129
 
92
130
  ##
@@ -161,6 +199,13 @@ module Gapic
161
199
  # @return [Boolean] Whether this error should be retried.
162
200
  #
163
201
  def retry_error? error
202
+ # Run predicate first. If it returns nil, decision is made on the basis of retry_codes.
203
+ # Otherwise return truthiness.
204
+ if @retry_predicate.respond_to? :call
205
+ result = @retry_predicate.call error
206
+ return !!result unless result.nil?
207
+ end
208
+
164
209
  (defined?(::GRPC) && error.is_a?(::GRPC::BadStatus) && retry_codes.include?(error.code)) ||
165
210
  (error.respond_to?(:response_status) &&
166
211
  retry_codes.include?(ErrorCodes.grpc_error_for(error.response_status)))
@@ -182,10 +227,12 @@ module Gapic
182
227
  #
183
228
  def apply_defaults retry_policy
184
229
  return unless retry_policy.is_a? Hash
185
- @retry_codes ||= convert_codes retry_policy[:retry_codes]
186
- @initial_delay ||= retry_policy[:initial_delay]
187
- @multiplier ||= retry_policy[:multiplier]
188
- @max_delay ||= retry_policy[:max_delay]
230
+ @retry_codes ||= convert_codes retry_policy[:retry_codes]
231
+ @initial_delay ||= retry_policy[:initial_delay]
232
+ @multiplier ||= retry_policy[:multiplier]
233
+ @max_delay ||= retry_policy[:max_delay]
234
+ @jitter ||= retry_policy[:jitter]
235
+ @retry_predicate ||= retry_policy[:retry_predicate]
189
236
 
190
237
  self
191
238
  end
@@ -197,30 +244,35 @@ module Gapic
197
244
  other.max_delay == max_delay &&
198
245
  other.multiplier == multiplier &&
199
246
  other.retry_codes == retry_codes &&
200
- other.timeout == timeout
247
+ other.timeout == timeout &&
248
+ other.jitter == jitter &&
249
+ other.retry_predicate == retry_predicate
201
250
  end
202
251
  alias == eql?
203
252
 
204
253
  # @private Hash code
205
254
  def hash
206
- [initial_delay, max_delay, multiplier, retry_codes, timeout].hash
255
+ [initial_delay, max_delay, multiplier, retry_codes, timeout, jitter, retry_predicate].hash
207
256
  end
208
257
 
209
258
  private
210
259
 
211
260
  # @private
261
+ # Perform the currently calculated delay, adding a jitter.
262
+ #
212
263
  # @return [Numeric] The performed delay.
213
264
  def delay!
265
+ delay_to_perform = [delay + Kernel.rand(0.0..jitter), max_delay].min
214
266
  if @mock_time
215
- @mock_time += delay
216
- @mock_delay_callback&.call delay
267
+ @mock_time += delay_to_perform
268
+ @mock_delay_callback&.call delay_to_perform
217
269
  else
218
- Kernel.sleep delay
270
+ Kernel.sleep delay_to_perform
219
271
  end
220
272
  end
221
273
 
222
274
  # @private
223
- # @return [Numeric] The new delay in seconds.
275
+ # @return [Numeric] The new delay (sans jitter) in seconds.
224
276
  def increment_delay!
225
277
  @delay = [delay * multiplier, max_delay].min
226
278
  end
@@ -14,6 +14,6 @@
14
14
 
15
15
  module Gapic
16
16
  module Common
17
- VERSION = "1.2.0".freeze
17
+ VERSION = "1.4.0".freeze
18
18
  end
19
19
  end
@@ -38,13 +38,15 @@ module Gapic
38
38
  # @param multiplier [Numeric] The delay scaling factor for each subsequent retry attempt.
39
39
  # @param max_delay [Numeric] Maximum delay in seconds.
40
40
  # @param timeout [Numeric] Timeout threshold value in seconds.
41
+ # @param jitter [Numeric] Random jitter added to the delay in seconds.
41
42
  #
42
- def initialize initial_delay: nil, multiplier: nil, max_delay: nil, timeout: nil
43
+ def initialize initial_delay: nil, multiplier: nil, max_delay: nil, timeout: nil, jitter: nil
43
44
  super(
44
45
  initial_delay: initial_delay || DEFAULT_INITIAL_DELAY,
45
46
  max_delay: max_delay || DEFAULT_MAX_DELAY,
46
47
  multiplier: multiplier || DEFAULT_MULTIPLIER,
47
- timeout: timeout || DEFAULT_TIMEOUT
48
+ timeout: timeout || DEFAULT_TIMEOUT,
49
+ jitter: jitter
48
50
  )
49
51
  end
50
52
  end
@@ -160,7 +160,16 @@ module Gapic
160
160
  request_body_field = request.send body_template.to_sym if request.respond_to? body_template.to_sym
161
161
  if request_body_field
162
162
  request_hash_without_uri.delete camel_name_for body_template
163
- body = request_body_field.to_json emit_defaults: true
163
+ # `emit_defaults` is only meaningful for, and only accepted by,
164
+ # `Google::Protobuf::MessageExts#to_json`. When the body template points at a
165
+ # scalar, enum or repeated field, `to_json` resolves to the json gem's
166
+ # `Object#to_json`, which has never accepted the keyword: json 2.x silently
167
+ # discarded unknown keywords, json 3.x raises ArgumentError.
168
+ body = if request_body_field.is_a? ::Google::Protobuf::MessageExts
169
+ request_body_field.to_json emit_defaults: true
170
+ else
171
+ request_body_field.to_json
172
+ end
164
173
  end
165
174
 
166
175
  query_params = build_query_params request_hash_without_uri
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gapic-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google API Authors
@@ -137,16 +137,22 @@ dependencies:
137
137
  name: grpc
138
138
  requirement: !ruby/object:Gem::Requirement
139
139
  requirements:
140
- - - "~>"
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '1.83'
143
+ - - "<"
141
144
  - !ruby/object:Gem::Version
142
- version: '1.66'
145
+ version: 2.a
143
146
  type: :runtime
144
147
  prerelease: false
145
148
  version_requirements: !ruby/object:Gem::Requirement
146
149
  requirements:
147
- - - "~>"
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '1.83'
153
+ - - "<"
148
154
  - !ruby/object:Gem::Version
149
- version: '1.66'
155
+ version: 2.a
150
156
  email:
151
157
  - googleapis-packages@google.com
152
158
  executables: []
@@ -213,7 +219,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
213
219
  requirements:
214
220
  - - ">="
215
221
  - !ruby/object:Gem::Version
216
- version: '3.1'
222
+ version: '3.2'
217
223
  required_rubygems_version: !ruby/object:Gem::Requirement
218
224
  requirements:
219
225
  - - ">="