gapic-common 1.4.0 → 1.5.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +2 -5
- data/lib/gapic/common/retry_policy.rb +34 -0
- data/lib/gapic/common/version.rb +1 -1
- data/lib/gapic/logging_concerns.rb +4 -0
- data/lib/gapic/rest/client_stub.rb +13 -3
- data/lib/gapic/rest/error.rb +4 -1
- data/lib/gapic/rest/grpc_transcoder.rb +39 -0
- 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 +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f9cc2de9bcbe93a62a1706acc73f6d37064bbde7a81a5c9fd434570ed8c6e22f
|
|
4
|
+
data.tar.gz: 3be68b0522ce0e3465bfb555820c7c73882f2e1bf36ac009fd4bb4f31c0bb3bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f958c2009dead1a09f0f20a9dda26f65d1698399657462b47b501297aff0e6af7496b4b0314237485bbb4f9a61d3866b9ee8e61ac26469c0ee69184c53cddd5b
|
|
7
|
+
data.tar.gz: e8fd4368fab545fbc33636d5999e73bd9f8b664006f5967929cd8e09b25cfafb22e34a17d58ce1481b94474d952d2c7add11ba7644ba77ffab23b98c2b83728a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### 1.5.0 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
#### Features
|
|
6
|
+
|
|
7
|
+
* Resumable Media Upload functionality implementation ([#72](https://github.com/googleapis/ruby-core-libraries/issues/72))
|
|
8
|
+
#### Documentation
|
|
9
|
+
|
|
10
|
+
* remove stale preview notice and disclaimer from READMEs ([#77](https://github.com/googleapis/ruby-core-libraries/issues/77))
|
|
11
|
+
|
|
12
|
+
### 1.4.1 (2026-09-28)
|
|
13
|
+
|
|
14
|
+
#### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* validate path parameters and prevent traversal/injection in REST transcoder ([#67](https://github.com/googleapis/ruby-core-libraries/issues/67))
|
|
17
|
+
|
|
3
18
|
### 1.4.0 (2026-09-23)
|
|
4
19
|
|
|
5
20
|
#### Features
|
data/README.md
CHANGED
|
@@ -61,9 +61,6 @@ See the {file:CONTRIBUTING.md CONTRIBUTING} documentation for more information o
|
|
|
61
61
|
|
|
62
62
|
## Versioning
|
|
63
63
|
|
|
64
|
-
This library
|
|
65
|
-
involved and let us know if you find it useful and we'll work towards a stable version.
|
|
64
|
+
This library follows [Semantic Versioning](http://semver.org/).
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
This is not an official Google product.
|
|
66
|
+
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
|
|
@@ -111,6 +117,34 @@ module Gapic
|
|
|
111
117
|
@retry_predicate
|
|
112
118
|
end
|
|
113
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
|
+
|
|
114
148
|
##
|
|
115
149
|
# Returns a duplicate in a non-executing state, i.e. with the deadline
|
|
116
150
|
# and current delay reset.
|
data/lib/gapic/common/version.rb
CHANGED
|
@@ -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 or injection 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.
|
|
@@ -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
|