gapic-common 1.3.0 → 1.6.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 +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +36 -12
- data/lib/gapic/common/retry_policy.rb +75 -9
- data/lib/gapic/common/version.rb +1 -1
- data/lib/gapic/logging_concerns.rb +9 -3
- data/lib/gapic/rest/client_stub.rb +13 -3
- data/lib/gapic/rest/error.rb +4 -1
- data/lib/gapic/rest/grpc_transcoder.rb +53 -3
- data/lib/gapic/rest/resumable_upload/core.rb +77 -0
- data/lib/gapic/rest/resumable_upload/data_types.rb +428 -0
- data/lib/gapic/rest/resumable_upload/driver/abridge.rb +168 -0
- data/lib/gapic/rest/resumable_upload/driver/retry_decider.rb +190 -0
- data/lib/gapic/rest/resumable_upload/driver/upload_log.rb +343 -0
- data/lib/gapic/rest/resumable_upload/driver.rb +855 -0
- data/lib/gapic/rest/resumable_upload/errors.rb +581 -0
- data/lib/gapic/rest/resumable_upload/events.rb +129 -0
- data/lib/gapic/rest/resumable_upload/instructions.rb +273 -0
- data/lib/gapic/rest/resumable_upload/retry_policies.rb +116 -0
- data/lib/gapic/rest/resumable_upload/rules.rb +1210 -0
- data/lib/gapic/rest/resumable_upload.rb +110 -0
- data/lib/gapic/rest.rb +2 -0
- data/lib/gapic/resumable_upload.rb +508 -0
- metadata +25 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b913235ab6a4508add68839ed5249a5593fe35543c12d7c5a0fd10da958d52d4
|
|
4
|
+
data.tar.gz: 25da91337dde92ebab37ce20f20d6795c3eb15f70480e488fb5a76e0c3e771ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '093cefa2ecd695bc76f1a9bc0b90934f946e8953576b110abb834e60efa6b849e684e39d86d0c02ca560fb52b221261e348c5bf4ff7caf1c2b45256c7ea3ff21'
|
|
7
|
+
data.tar.gz: 12e1f11af015c7ee7df9dc3049123b998e3a3aa0d6fb7cf39cb5804620a9bd1883870b05526d8d1418d7162c2f883b1fdafa2f1731ef8ea216a53d8e884f3979
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### 1.6.1 (2026-10-29)
|
|
4
|
+
|
|
5
|
+
#### Documentation
|
|
6
|
+
|
|
7
|
+
* link to the published Ruby version support policy ([#91](https://github.com/googleapis/ruby-core-libraries/issues/91))
|
|
8
|
+
|
|
9
|
+
### 1.6.0 (2026-10-22)
|
|
10
|
+
|
|
11
|
+
#### Features
|
|
12
|
+
|
|
13
|
+
* require grpc >= 1.84.1 ([#90](https://github.com/googleapis/ruby-core-libraries/issues/90))
|
|
14
|
+
#### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* ignore inherited Service constants in normalize_service ([#71](https://github.com/googleapis/ruby-core-libraries/issues/71)) ([47c7e98](https://github.com/googleapis/ruby-core-libraries/commit/47c7e9854904caa59955aab31a6fdacd0af200b8)), refs [#70](https://github.com/googleapis/ruby-core-libraries/issues/70)
|
|
17
|
+
|
|
18
|
+
### 1.5.1 (2026-09-30)
|
|
19
|
+
|
|
20
|
+
#### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* tighten path template matching in the REST transcoder ([#83](https://github.com/googleapis/ruby-core-libraries/issues/83))
|
|
23
|
+
|
|
24
|
+
### 1.5.0 (2026-09-29)
|
|
25
|
+
|
|
26
|
+
#### Features
|
|
27
|
+
|
|
28
|
+
* Resumable Media Upload functionality implementation ([#72](https://github.com/googleapis/ruby-core-libraries/issues/72))
|
|
29
|
+
#### Documentation
|
|
30
|
+
|
|
31
|
+
* remove stale preview notice and disclaimer from READMEs ([#77](https://github.com/googleapis/ruby-core-libraries/issues/77))
|
|
32
|
+
|
|
33
|
+
### 1.4.1 (2026-09-28)
|
|
34
|
+
|
|
35
|
+
#### Bug Fixes
|
|
36
|
+
|
|
37
|
+
* validate path parameters and prevent traversal/injection in REST transcoder ([#67](https://github.com/googleapis/ruby-core-libraries/issues/67))
|
|
38
|
+
|
|
39
|
+
### 1.4.0 (2026-09-23)
|
|
40
|
+
|
|
41
|
+
#### Features
|
|
42
|
+
|
|
43
|
+
* add custom predicate matching to RetryPolicy ([#59](https://github.com/googleapis/ruby-core-libraries/issues/59))
|
|
44
|
+
#### Bug Fixes
|
|
45
|
+
|
|
46
|
+
* require grpc >= 1.83 for post-quantum key exchange ([#73](https://github.com/googleapis/ruby-core-libraries/issues/73))
|
|
47
|
+
* only use emit_default for proto messages ([#75](https://github.com/googleapis/ruby-core-libraries/issues/75))
|
|
48
|
+
|
|
3
49
|
### 1.3.0 (2026-03-05)
|
|
4
50
|
|
|
5
51
|
#### Features
|
data/README.md
CHANGED
|
@@ -15,14 +15,41 @@ convenient and idiomatic API surface to callers.
|
|
|
15
15
|
|
|
16
16
|
## Supported Ruby Versions
|
|
17
17
|
|
|
18
|
-
This library is supported on Ruby 3.
|
|
18
|
+
This library is supported on Ruby 3.2+.
|
|
19
19
|
|
|
20
|
-
Google provides official support for Ruby versions
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
Google provides official support for Ruby versions as described in the
|
|
21
|
+
[Supported Ruby versions](https://docs.cloud.google.com/ruby/getting-started/supported-ruby-versions)
|
|
22
|
+
policy. Older versions of Ruby _may_ still work, but are unsupported and not
|
|
23
|
+
recommended.
|
|
24
|
+
|
|
25
|
+
## Post-Quantum Key Exchange
|
|
26
|
+
|
|
27
|
+
Clients built on this library negotiate the hybrid post-quantum key exchange
|
|
28
|
+
group `X25519MLKEM768` — classical X25519 paired with ML-KEM-768 ([NIST FIPS
|
|
29
|
+
203][]) — over TLS 1.3. No application code changes are required, because the
|
|
30
|
+
handshake belongs to the transport layer. The two transports source that
|
|
31
|
+
support differently:
|
|
32
|
+
|
|
33
|
+
| Transport | Cryptographic provider | Requirement | Enforced by this gem? |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| gRPC | BoringSSL, vendored inside the `grpc` gem | `grpc >= 1.83` | Yes |
|
|
36
|
+
| REST | Host system OpenSSL, via `Net::HTTP` and Faraday | OpenSSL `>= 3.5` | No |
|
|
37
|
+
|
|
38
|
+
gRPC clients get post-quantum key exchange automatically: `gapic-common`
|
|
39
|
+
depends on `grpc >= 1.83`, the first release to offer `X25519MLKEM768` in the
|
|
40
|
+
TLS ClientHello by default.
|
|
41
|
+
|
|
42
|
+
The REST requirement cannot be expressed as a gem dependency. Ruby's `openssl`
|
|
43
|
+
is a default gem bound to whatever `libssl` the host provides, and ML-KEM first
|
|
44
|
+
ships in OpenSSL 3.5. On an older host, REST connections negotiate classical
|
|
45
|
+
X25519 instead — a safe fallback rather than an error, but not post-quantum. To
|
|
46
|
+
check the OpenSSL your Ruby is linked against:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
ruby -ropenssl -e 'puts OpenSSL::OPENSSL_LIBRARY_VERSION'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
[NIST FIPS 203]: https://csrc.nist.gov/pubs/fips/203/final
|
|
26
53
|
|
|
27
54
|
## Contributing
|
|
28
55
|
|
|
@@ -32,9 +59,6 @@ See the {file:CONTRIBUTING.md CONTRIBUTING} documentation for more information o
|
|
|
32
59
|
|
|
33
60
|
## Versioning
|
|
34
61
|
|
|
35
|
-
This library
|
|
36
|
-
involved and let us know if you find it useful and we'll work towards a stable version.
|
|
37
|
-
|
|
38
|
-
## Disclaimer
|
|
62
|
+
This library follows [Semantic Versioning](http://semver.org/).
|
|
39
63
|
|
|
40
|
-
This is not
|
|
64
|
+
This library is considered to be stable and will not have backwards-incompatible changes introduced in subsequent minor releases.
|
|
@@ -19,6 +19,12 @@ module Gapic
|
|
|
19
19
|
##
|
|
20
20
|
# Gapic Common retry policy base class.
|
|
21
21
|
#
|
|
22
|
+
# A policy distinguishes "set to this value" from "never set". Every setting is stored as `nil`
|
|
23
|
+
# until someone supplies it, and the reader for each substitutes the corresponding `DEFAULT_`
|
|
24
|
+
# constant on the way out, so a reader can never say which of the two happened. That distinction is
|
|
25
|
+
# what lets {#apply_defaults} fill in gaps without overwriting a caller's choices, and {#overrides}
|
|
26
|
+
# report what a caller actually asked for.
|
|
27
|
+
#
|
|
22
28
|
class RetryPolicy
|
|
23
29
|
# @return [Numeric] Default initial delay in seconds.
|
|
24
30
|
DEFAULT_INITIAL_DELAY = 1
|
|
@@ -45,9 +51,17 @@ module Gapic
|
|
|
45
51
|
# @param retry_codes [Array<String|Integer>] List of retry codes.
|
|
46
52
|
# @param timeout [Numeric] Timeout threshold value in seconds.
|
|
47
53
|
# @param jitter [Numeric] Random jitter added to the delay in seconds.
|
|
54
|
+
# @param retry_predicate [Proc, nil] The predicate to evaluate whether to retry on a given error. Optional.
|
|
55
|
+
# If the predicate is specified, it is run first. If it returns nil, the decision on
|
|
56
|
+
# whether to retry is made on the basis of retry_codes. Otherwise, the truthiness of
|
|
57
|
+
# the return value determines whether to retry.
|
|
48
58
|
#
|
|
49
|
-
def initialize initial_delay: nil, max_delay: nil, multiplier: nil, retry_codes: nil, timeout: nil,
|
|
59
|
+
def initialize initial_delay: nil, max_delay: nil, multiplier: nil, retry_codes: nil, timeout: nil,
|
|
60
|
+
jitter: nil, retry_predicate: nil
|
|
50
61
|
raise ArgumentError, "jitter cannot be negative" if jitter&.negative?
|
|
62
|
+
if retry_predicate && !retry_predicate.respond_to?(:call)
|
|
63
|
+
raise ArgumentError, "retry_predicate must respond to :call"
|
|
64
|
+
end
|
|
51
65
|
|
|
52
66
|
# Instance values are set as `nil` to determine whether values are overriden from default.
|
|
53
67
|
@initial_delay = initial_delay
|
|
@@ -56,6 +70,7 @@ module Gapic
|
|
|
56
70
|
@retry_codes = convert_codes retry_codes
|
|
57
71
|
@timeout = timeout
|
|
58
72
|
@jitter = jitter
|
|
73
|
+
@retry_predicate = retry_predicate
|
|
59
74
|
start!
|
|
60
75
|
end
|
|
61
76
|
|
|
@@ -89,6 +104,47 @@ module Gapic
|
|
|
89
104
|
@jitter || DEFAULT_JITTER
|
|
90
105
|
end
|
|
91
106
|
|
|
107
|
+
##
|
|
108
|
+
# The predicate to evaluate whether to retry on a given error. Optional.
|
|
109
|
+
#
|
|
110
|
+
# When a predicate is specified:
|
|
111
|
+
# 1. The predicate is executed first on the error.
|
|
112
|
+
# 2. If it returns nil, the decision on whether to retry is made on the basis of retry_codes.
|
|
113
|
+
# 3. Otherwise, the truthiness of the return value determines whether to retry.
|
|
114
|
+
#
|
|
115
|
+
# @return [Proc, nil]
|
|
116
|
+
def retry_predicate
|
|
117
|
+
@retry_predicate
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# @private
|
|
122
|
+
# The settings this policy actually carries, as keyword arguments for {RetryPolicy.initialize}.
|
|
123
|
+
#
|
|
124
|
+
# A key is present only if that setting was explicitly supplied; a key that was never set is
|
|
125
|
+
# absent rather than `nil`. This is what the readers cannot tell you — {#max_delay} returns
|
|
126
|
+
# {DEFAULT_MAX_DELAY} whether the caller chose that number or said nothing — so it is the only
|
|
127
|
+
# safe way to carry one policy's settings onto another without dragging defaults along, and
|
|
128
|
+
# without mistaking a deliberate choice that happens to equal a default for silence.
|
|
129
|
+
#
|
|
130
|
+
# An empty `retry_codes` list counts as unset. It retries nothing, which is exactly what an
|
|
131
|
+
# unset list does, so there is nothing for it to carry.
|
|
132
|
+
#
|
|
133
|
+
# @return [Hash{Symbol=>Object}] Explicitly set settings only
|
|
134
|
+
def overrides
|
|
135
|
+
# Assigns nil, and so omits the key, when the list is absent or empty.
|
|
136
|
+
retry_codes = @retry_codes unless @retry_codes.nil? || @retry_codes.empty?
|
|
137
|
+
{
|
|
138
|
+
initial_delay: @initial_delay,
|
|
139
|
+
max_delay: @max_delay,
|
|
140
|
+
multiplier: @multiplier,
|
|
141
|
+
retry_codes: retry_codes,
|
|
142
|
+
timeout: @timeout,
|
|
143
|
+
jitter: @jitter,
|
|
144
|
+
retry_predicate: @retry_predicate
|
|
145
|
+
}.compact
|
|
146
|
+
end
|
|
147
|
+
|
|
92
148
|
##
|
|
93
149
|
# Returns a duplicate in a non-executing state, i.e. with the deadline
|
|
94
150
|
# and current delay reset.
|
|
@@ -101,7 +157,8 @@ module Gapic
|
|
|
101
157
|
multiplier: @multiplier,
|
|
102
158
|
retry_codes: @retry_codes,
|
|
103
159
|
timeout: @timeout,
|
|
104
|
-
jitter: @jitter
|
|
160
|
+
jitter: @jitter,
|
|
161
|
+
retry_predicate: @retry_predicate
|
|
105
162
|
end
|
|
106
163
|
|
|
107
164
|
##
|
|
@@ -176,6 +233,13 @@ module Gapic
|
|
|
176
233
|
# @return [Boolean] Whether this error should be retried.
|
|
177
234
|
#
|
|
178
235
|
def retry_error? error
|
|
236
|
+
# Run predicate first. If it returns nil, decision is made on the basis of retry_codes.
|
|
237
|
+
# Otherwise return truthiness.
|
|
238
|
+
if @retry_predicate.respond_to? :call
|
|
239
|
+
result = @retry_predicate.call error
|
|
240
|
+
return !!result unless result.nil?
|
|
241
|
+
end
|
|
242
|
+
|
|
179
243
|
(defined?(::GRPC) && error.is_a?(::GRPC::BadStatus) && retry_codes.include?(error.code)) ||
|
|
180
244
|
(error.respond_to?(:response_status) &&
|
|
181
245
|
retry_codes.include?(ErrorCodes.grpc_error_for(error.response_status)))
|
|
@@ -197,11 +261,12 @@ module Gapic
|
|
|
197
261
|
#
|
|
198
262
|
def apply_defaults retry_policy
|
|
199
263
|
return unless retry_policy.is_a? Hash
|
|
200
|
-
@retry_codes
|
|
201
|
-
@initial_delay
|
|
202
|
-
@multiplier
|
|
203
|
-
@max_delay
|
|
204
|
-
@jitter
|
|
264
|
+
@retry_codes ||= convert_codes retry_policy[:retry_codes]
|
|
265
|
+
@initial_delay ||= retry_policy[:initial_delay]
|
|
266
|
+
@multiplier ||= retry_policy[:multiplier]
|
|
267
|
+
@max_delay ||= retry_policy[:max_delay]
|
|
268
|
+
@jitter ||= retry_policy[:jitter]
|
|
269
|
+
@retry_predicate ||= retry_policy[:retry_predicate]
|
|
205
270
|
|
|
206
271
|
self
|
|
207
272
|
end
|
|
@@ -214,13 +279,14 @@ module Gapic
|
|
|
214
279
|
other.multiplier == multiplier &&
|
|
215
280
|
other.retry_codes == retry_codes &&
|
|
216
281
|
other.timeout == timeout &&
|
|
217
|
-
other.jitter == jitter
|
|
282
|
+
other.jitter == jitter &&
|
|
283
|
+
other.retry_predicate == retry_predicate
|
|
218
284
|
end
|
|
219
285
|
alias == eql?
|
|
220
286
|
|
|
221
287
|
# @private Hash code
|
|
222
288
|
def hash
|
|
223
|
-
[initial_delay, max_delay, multiplier, retry_codes, timeout, jitter].hash
|
|
289
|
+
[initial_delay, max_delay, multiplier, retry_codes, timeout, jitter, retry_predicate].hash
|
|
224
290
|
end
|
|
225
291
|
|
|
226
292
|
private
|
data/lib/gapic/common/version.rb
CHANGED
|
@@ -67,6 +67,10 @@ module Gapic
|
|
|
67
67
|
log(Logger::DEBUG, &)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
def warn(&)
|
|
71
|
+
log(Logger::WARN, &)
|
|
72
|
+
end
|
|
73
|
+
|
|
70
74
|
##
|
|
71
75
|
# @private
|
|
72
76
|
# Builder for a log entry, passed to {StubLogger#log}.
|
|
@@ -200,12 +204,14 @@ module Gapic
|
|
|
200
204
|
input
|
|
201
205
|
when Class
|
|
202
206
|
mod = input.name.split("::")[..-2].inject(Object) { |m, n| m.const_get n }
|
|
203
|
-
|
|
204
|
-
|
|
207
|
+
# inherit: false so a top-level ::Service is not mistaken for the
|
|
208
|
+
# generated gRPC Service sibling that REST stubs do not define.
|
|
209
|
+
if mod.const_defined? "Service", false
|
|
210
|
+
mod.const_get("Service", false).service_name
|
|
205
211
|
else
|
|
206
212
|
name_segments = input.name.split("::")[..-3]
|
|
207
213
|
mod = name_segments.inject(Object) { |m, n| m.const_get n }
|
|
208
|
-
name_segments.join "." if mod.const_defined? "Rest"
|
|
214
|
+
name_segments.join "." if mod.const_defined? "Rest", false
|
|
209
215
|
end
|
|
210
216
|
end
|
|
211
217
|
end
|
|
@@ -287,17 +287,27 @@ module Gapic
|
|
|
287
287
|
entry.set "requestId", request_id
|
|
288
288
|
entry.message = "Sending request to #{entry.service}.#{method_name} (try #{try_number})"
|
|
289
289
|
end
|
|
290
|
-
|
|
290
|
+
body_str = body.to_s
|
|
291
291
|
metadata = metadata.to_h rescue {}
|
|
292
|
-
return if
|
|
292
|
+
return if body_str.empty? && metadata.empty?
|
|
293
293
|
stub_logger.debug do |entry|
|
|
294
294
|
entry.set "requestId", request_id
|
|
295
|
-
entry.set "request",
|
|
295
|
+
entry.set "request", abridge_request_body(body_str)
|
|
296
296
|
entry.set "headers", metadata
|
|
297
297
|
entry.message = "(request payload as JSON)"
|
|
298
298
|
end
|
|
299
299
|
end
|
|
300
300
|
|
|
301
|
+
def abridge_request_body body_str
|
|
302
|
+
utf8_body = body_str.dup.force_encoding Encoding::UTF_8
|
|
303
|
+
if body_str.bytesize > 1024 || !utf8_body.valid_encoding?
|
|
304
|
+
prefix_hex = body_str.byteslice(0, 32).unpack1 "H*"
|
|
305
|
+
"<#{body_str.bytesize} bytes, first 32: #{prefix_hex}>"
|
|
306
|
+
else
|
|
307
|
+
utf8_body
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
301
311
|
def log_response method_name, request_id, try_number, response, is_server_streaming
|
|
302
312
|
return unless stub_logger&.enabled?
|
|
303
313
|
stub_logger.info do |entry|
|
data/lib/gapic/rest/error.rb
CHANGED
|
@@ -22,6 +22,9 @@ module Gapic
|
|
|
22
22
|
module Rest
|
|
23
23
|
# Gapic REST exception class
|
|
24
24
|
class Error < ::Gapic::Common::Error
|
|
25
|
+
# @private
|
|
26
|
+
REST_ERROR_PREFIX = "An error has occurred when making a REST request".freeze
|
|
27
|
+
|
|
25
28
|
# @return [Integer, nil] the http status code for the error
|
|
26
29
|
attr_reader :status_code
|
|
27
30
|
# @return [Object, nil] the text representation of status as parsed from the response body
|
|
@@ -79,7 +82,7 @@ module Gapic
|
|
|
79
82
|
|
|
80
83
|
if err.response_body
|
|
81
84
|
msg, code, status, details = try_parse_from_body err.response_body
|
|
82
|
-
message = "
|
|
85
|
+
message = "#{REST_ERROR_PREFIX}: #{msg}" unless msg.nil?
|
|
83
86
|
status_code = code unless code.nil?
|
|
84
87
|
end
|
|
85
88
|
|
|
@@ -111,12 +111,14 @@ module Gapic
|
|
|
111
111
|
# @return [Hash{String, String}]
|
|
112
112
|
# Name to value hash of the variables for the uri template expansion.
|
|
113
113
|
# The values are percent-escaped with slashes potentially preserved.
|
|
114
|
+
# @raise [Gapic::Common::Error] If any parameter value fails path traversal validation.
|
|
114
115
|
def bind_uri_values! http_binding, request_hash
|
|
115
116
|
http_binding.field_bindings.to_h do |field_binding|
|
|
116
117
|
field_path_camel = field_binding.field_path.split(".").map { |part| camel_name_for part }.join(".")
|
|
117
118
|
field_value = extract_scalar_value! request_hash, field_path_camel, field_binding.regex
|
|
118
119
|
|
|
119
120
|
if field_value
|
|
121
|
+
validate_field_binding! field_binding, field_value
|
|
120
122
|
field_value = field_value.split("/").map { |segment| percent_escape segment }.join("/")
|
|
121
123
|
end
|
|
122
124
|
|
|
@@ -124,6 +126,43 @@ module Gapic
|
|
|
124
126
|
end
|
|
125
127
|
end
|
|
126
128
|
|
|
129
|
+
# Validates a user-supplied parameter value bound to a standard (*) or path (**) URI template variable
|
|
130
|
+
# to prevent directory traversal exploits.
|
|
131
|
+
#
|
|
132
|
+
# @param field_binding [HttpBinding::FieldBinding] The field binding template metadata.
|
|
133
|
+
# @param field_value [String] The parameter value to validate.
|
|
134
|
+
# @raise [Gapic::Common::Error] If validation fails.
|
|
135
|
+
def validate_field_binding! field_binding, field_value
|
|
136
|
+
validate_path_binding! field_binding, field_value
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Validates standard (*) and path (**) parameters by ensuring that no segment in the parameter
|
|
140
|
+
# value is a directory traversal segment (. or ..).
|
|
141
|
+
#
|
|
142
|
+
# Validation Mechanism:
|
|
143
|
+
# 1. Splits the parameter value by slash (`/`) using `-1` limit to preserve all segments.
|
|
144
|
+
# 2. Checks each segment. If any segment matches `.` or `..`, it immediately raises
|
|
145
|
+
# a `Gapic::Common::Error`, aborting the request.
|
|
146
|
+
# 3. Empty segments (e.g. duplicate slashes `//` or trailing slashes `/`) are allowed
|
|
147
|
+
# by this validator and passed to the server, which handles normalization or returns 400.
|
|
148
|
+
#
|
|
149
|
+
# @param field_binding [HttpBinding::FieldBinding] The field binding template metadata.
|
|
150
|
+
# @param field_value [String] The parameter value to validate.
|
|
151
|
+
# @raise [Gapic::Common::Error] If validation fails.
|
|
152
|
+
def validate_path_binding! field_binding, field_value
|
|
153
|
+
segments = field_value.split("/", -1)
|
|
154
|
+
segments.each do |segment|
|
|
155
|
+
next unless [".", ".."].include? segment
|
|
156
|
+
if field_binding.preserve_slashes
|
|
157
|
+
raise ::Gapic::Common::Error,
|
|
158
|
+
"Value for #{field_binding.field_path} must not contain segments that are exactly '#{segment}'."
|
|
159
|
+
else
|
|
160
|
+
raise ::Gapic::Common::Error,
|
|
161
|
+
"Invalid value for #{field_binding.field_path} '#{segment}'."
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
127
166
|
# Percent-escapes a string.
|
|
128
167
|
# @param str [String] String to escape.
|
|
129
168
|
# @return [String] Escaped string.
|
|
@@ -160,7 +199,16 @@ module Gapic
|
|
|
160
199
|
request_body_field = request.send body_template.to_sym if request.respond_to? body_template.to_sym
|
|
161
200
|
if request_body_field
|
|
162
201
|
request_hash_without_uri.delete camel_name_for body_template
|
|
163
|
-
|
|
202
|
+
# `emit_defaults` is only meaningful for, and only accepted by,
|
|
203
|
+
# `Google::Protobuf::MessageExts#to_json`. When the body template points at a
|
|
204
|
+
# scalar, enum or repeated field, `to_json` resolves to the json gem's
|
|
205
|
+
# `Object#to_json`, which has never accepted the keyword: json 2.x silently
|
|
206
|
+
# discarded unknown keywords, json 3.x raises ArgumentError.
|
|
207
|
+
body = if request_body_field.is_a? ::Google::Protobuf::MessageExts
|
|
208
|
+
request_body_field.to_json emit_defaults: true
|
|
209
|
+
else
|
|
210
|
+
request_body_field.to_json
|
|
211
|
+
end
|
|
164
212
|
end
|
|
165
213
|
|
|
166
214
|
query_params = build_query_params request_hash_without_uri
|
|
@@ -204,7 +252,7 @@ module Gapic
|
|
|
204
252
|
# Returns nil if:
|
|
205
253
|
# - the field is not found
|
|
206
254
|
# - the field is a Message or an array,
|
|
207
|
-
# - the regex does not match
|
|
255
|
+
# - the regex does not match the entire string representation
|
|
208
256
|
# @param request_hash [Hash]
|
|
209
257
|
# A hash of the GRPC request or the sub-request with the unset
|
|
210
258
|
# proto3_optional fields removed.
|
|
@@ -218,7 +266,9 @@ module Gapic
|
|
|
218
266
|
|
|
219
267
|
# Covers the case where in `foo.bar.baz`, `baz` is still a submessage or an array.
|
|
220
268
|
return nil if value.is_a?(::Hash) || value.is_a?(::Array)
|
|
221
|
-
|
|
269
|
+
str = value.to_s
|
|
270
|
+
match = regex.match str
|
|
271
|
+
str if match && match.pre_match.empty? && match.post_match.empty?
|
|
222
272
|
end
|
|
223
273
|
|
|
224
274
|
# Finds a value in the hash by path.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2026 Google LLC
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require "gapic/rest/resumable_upload/data_types"
|
|
18
|
+
require "gapic/rest/resumable_upload/rules"
|
|
19
|
+
|
|
20
|
+
module Gapic
|
|
21
|
+
module Rest
|
|
22
|
+
module ResumableUpload
|
|
23
|
+
##
|
|
24
|
+
# @private
|
|
25
|
+
# State machine container holding the immutable State snapshot.
|
|
26
|
+
# Contains zero protocol branching logic and zero side-effects.
|
|
27
|
+
#
|
|
28
|
+
# The middle tier of the three-tier design: `Driver` executes side effects, {Rules} decides transitions,
|
|
29
|
+
# and Core holds the {State} between the two. See {Rules} for the protocol narrative and state graph, and
|
|
30
|
+
# `design/resumable_upload/implementation-guide.md` section 1 for the tier boundaries.
|
|
31
|
+
#
|
|
32
|
+
class Core
|
|
33
|
+
# @private
|
|
34
|
+
# @return [State] Current immutable state snapshot
|
|
35
|
+
attr_reader :state
|
|
36
|
+
|
|
37
|
+
# @private
|
|
38
|
+
# @return [Decision, nil] Decision emitted during the last dispatch
|
|
39
|
+
attr_reader :last_decision
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# @private
|
|
43
|
+
# Initializes a Core state machine container.
|
|
44
|
+
#
|
|
45
|
+
# @param config [StartUploadConfig, ResumeUploadConfig] Upload session configuration
|
|
46
|
+
#
|
|
47
|
+
def initialize config
|
|
48
|
+
@config = config
|
|
49
|
+
@last_decision = nil
|
|
50
|
+
@state = State.new(
|
|
51
|
+
status: :initializing,
|
|
52
|
+
upload_url: nil,
|
|
53
|
+
offset: 0,
|
|
54
|
+
chunk_size: config.chunk_size || Rules::DEFAULT_CHUNK_SIZE,
|
|
55
|
+
chunk_granularity: nil,
|
|
56
|
+
in_flight_length: 0,
|
|
57
|
+
last_error: nil
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# @private
|
|
63
|
+
# Dispatches event to Rules and updates internal state snapshot.
|
|
64
|
+
#
|
|
65
|
+
# @param event [Object] Input event
|
|
66
|
+
# @return [Array<Object>] Driver instructions
|
|
67
|
+
#
|
|
68
|
+
def dispatch event
|
|
69
|
+
decision = Rules.decide @state, event, @config
|
|
70
|
+
@state = decision.next_state
|
|
71
|
+
@last_decision = decision
|
|
72
|
+
decision.instructions
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|