gapic-common 1.4.1 → 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 +9 -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/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,14 @@
|
|
|
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
|
+
|
|
3
12
|
### 1.4.1 (2026-09-28)
|
|
4
13
|
|
|
5
14
|
#### Bug Fixes
|
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
|
|
|
@@ -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
|
|
@@ -0,0 +1,428 @@
|
|
|
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
|
+
# rubocop:disable Metrics/ModuleLength
|
|
18
|
+
|
|
19
|
+
module Gapic
|
|
20
|
+
module Rest
|
|
21
|
+
module ResumableUpload
|
|
22
|
+
##
|
|
23
|
+
# @private
|
|
24
|
+
# Configuration members shared by {StartUploadConfig} and {ResumeUploadConfig}, in the order both
|
|
25
|
+
# definitions splat them.
|
|
26
|
+
#
|
|
27
|
+
# The two config types are deliberately *flat*: {Core}, {Rules} and {Driver} read every member
|
|
28
|
+
# straight off `config`. Nesting the shared members inside a common object would turn every
|
|
29
|
+
# `config.upload_size` into `config.common.upload_size` at some thirty call sites for no behavioural
|
|
30
|
+
# gain, so they are spliced into each `Data.define` instead.
|
|
31
|
+
#
|
|
32
|
+
# * `stream` [IO] Binary input stream to upload. Required.
|
|
33
|
+
# * `upload_size` [Integer, nil] Total upload bytes if known upfront.
|
|
34
|
+
# * `content_type` [String, nil] MIME type of uploaded media.
|
|
35
|
+
# * `timeout` [Numeric, nil] Total upload timeout in seconds (zero or negative is treated as nil).
|
|
36
|
+
# * `control_plane_retry_policy` [Gapic::Common::RetryPolicy, Hash, nil] Retry policy for control
|
|
37
|
+
# commands (query, cancel).
|
|
38
|
+
# * `data_plane_retry_policy` [Gapic::Common::RetryPolicy, Hash, nil] Retry policy for data commands
|
|
39
|
+
# (upload, finalize).
|
|
40
|
+
# * `on_progress` [Proc, nil] Callback invoked as `->(progress)` with a {Progress} instance.
|
|
41
|
+
#
|
|
42
|
+
# Every retry policy member, here and in the per-run configs, follows the same convention: a
|
|
43
|
+
# {Gapic::Common::RetryPolicy} replaces the default policy outright, while a Hash overrides only the
|
|
44
|
+
# settings it names and leaves the remaining defaults — including retry codes and predicates — in place.
|
|
45
|
+
#
|
|
46
|
+
COMMON_MEMBERS = [
|
|
47
|
+
:stream,
|
|
48
|
+
:upload_size,
|
|
49
|
+
:content_type,
|
|
50
|
+
:timeout,
|
|
51
|
+
:control_plane_retry_policy,
|
|
52
|
+
:data_plane_retry_policy,
|
|
53
|
+
:on_progress
|
|
54
|
+
].freeze
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# @private
|
|
58
|
+
# Header names a caller may not use in `initial_headers`, lowercased for comparison.
|
|
59
|
+
#
|
|
60
|
+
# These five headers are protocol machinery the driver owns: the protocol identifier, the command
|
|
61
|
+
# verb, the byte offset, and the content descriptors derived from `content_type` and `upload_size`.
|
|
62
|
+
# `x-goog-upload-offset` is included for completeness even though initiation never sets an offset:
|
|
63
|
+
# supplying an offset at initiation is meaningless and indicates a confused caller. Pass-through
|
|
64
|
+
# headers such as `X-Goog-Upload-Header-Content-Disposition` remain permitted.
|
|
65
|
+
#
|
|
66
|
+
# See `Driver#start_headers`, which builds the initiation headers this list protects.
|
|
67
|
+
#
|
|
68
|
+
# @return [Array<String>]
|
|
69
|
+
RESERVED_INITIAL_HEADERS = [
|
|
70
|
+
"x-goog-upload-protocol",
|
|
71
|
+
"x-goog-upload-command",
|
|
72
|
+
"x-goog-upload-offset",
|
|
73
|
+
"x-goog-upload-header-content-type",
|
|
74
|
+
"x-goog-upload-header-content-length"
|
|
75
|
+
].freeze
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# @private
|
|
79
|
+
# Immutable configuration for a run that initiates a new upload session, i.e.
|
|
80
|
+
# {Gapic::ResumableUpload#start}.
|
|
81
|
+
#
|
|
82
|
+
# Carries {COMMON_MEMBERS} plus the members only an initiating run uses.
|
|
83
|
+
#
|
|
84
|
+
# @!attribute [r] initial_url
|
|
85
|
+
# @return [String] Initial endpoint URI for session initiation
|
|
86
|
+
# @!attribute [r] initial_body
|
|
87
|
+
# @return [String, nil] Request payload for session initiation
|
|
88
|
+
# @!attribute [r] initial_headers
|
|
89
|
+
# @return [Hash<String, String>] Additional headers for initiation, merged over the driver's
|
|
90
|
+
# own headers. Keys in {RESERVED_INITIAL_HEADERS} are rejected in any casing; use
|
|
91
|
+
# `content_type` and `upload_size` to shape those.
|
|
92
|
+
# @!attribute [r] chunk_size
|
|
93
|
+
# @return [Integer, nil] Requested chunk size in bytes, aligned to the granularity the server
|
|
94
|
+
# reports during initiation. A resumed run takes its chunk size from {ResumeUploadConfig}.
|
|
95
|
+
# @!attribute [r] start_retry_policy
|
|
96
|
+
# @return [Gapic::Common::RetryPolicy, Hash, nil] Retry policy for session initiation
|
|
97
|
+
#
|
|
98
|
+
StartUploadConfig = Data.define(
|
|
99
|
+
*COMMON_MEMBERS,
|
|
100
|
+
:initial_url,
|
|
101
|
+
:initial_body,
|
|
102
|
+
:initial_headers,
|
|
103
|
+
:chunk_size,
|
|
104
|
+
:start_retry_policy
|
|
105
|
+
) do
|
|
106
|
+
##
|
|
107
|
+
# @private
|
|
108
|
+
# Initializes a new upload configuration.
|
|
109
|
+
#
|
|
110
|
+
# @param initial_url [String] Initial endpoint URI for session initiation
|
|
111
|
+
# @param stream [IO] Binary input stream to upload
|
|
112
|
+
# @param initial_body [String, nil] Request payload for session initiation
|
|
113
|
+
# @param initial_headers [Hash<String, String>] Additional headers for initiation. Keys in
|
|
114
|
+
# {RESERVED_INITIAL_HEADERS} are rejected in any casing; use `content_type` and `upload_size`
|
|
115
|
+
# to shape those.
|
|
116
|
+
# @param upload_size [Integer, nil] Total upload bytes if known upfront
|
|
117
|
+
# @param chunk_size [Integer, nil] Requested chunk size in bytes
|
|
118
|
+
# @param content_type [String, nil] MIME type of uploaded media
|
|
119
|
+
# @param timeout [Numeric, nil] Total upload timeout in seconds (zero/negative values treated as nil)
|
|
120
|
+
# @param start_retry_policy [Gapic::Common::RetryPolicy, Hash, nil] Retry policy for session initiation
|
|
121
|
+
# @param control_plane_retry_policy [Gapic::Common::RetryPolicy, Hash, nil] Retry policy for control commands
|
|
122
|
+
# @param data_plane_retry_policy [Gapic::Common::RetryPolicy, Hash, nil] Retry policy for data commands
|
|
123
|
+
# @param on_progress [Proc, nil] Callback invoked as `->(progress)` with a {Progress} instance
|
|
124
|
+
# @raise [ArgumentError] If required arguments are missing or invalid
|
|
125
|
+
#
|
|
126
|
+
def initialize initial_url:,
|
|
127
|
+
stream:,
|
|
128
|
+
initial_body: nil,
|
|
129
|
+
initial_headers: {},
|
|
130
|
+
upload_size: nil,
|
|
131
|
+
chunk_size: nil,
|
|
132
|
+
content_type: nil,
|
|
133
|
+
timeout: nil,
|
|
134
|
+
start_retry_policy: nil,
|
|
135
|
+
control_plane_retry_policy: nil,
|
|
136
|
+
data_plane_retry_policy: nil,
|
|
137
|
+
on_progress: nil
|
|
138
|
+
raise ArgumentError, "initial_url is required" if initial_url.nil? || initial_url.to_s.strip.empty?
|
|
139
|
+
raise ArgumentError, "stream is required" if stream.nil?
|
|
140
|
+
reserved = (initial_headers || {}).keys.find do |key|
|
|
141
|
+
RESERVED_INITIAL_HEADERS.include? key.to_s.downcase
|
|
142
|
+
end
|
|
143
|
+
if reserved
|
|
144
|
+
raise ArgumentError,
|
|
145
|
+
"initial_headers must not set protocol header #{reserved.inspect}; " \
|
|
146
|
+
"use content_type and upload_size instead"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
super(
|
|
150
|
+
initial_url: initial_url,
|
|
151
|
+
initial_body: initial_body,
|
|
152
|
+
initial_headers: initial_headers || {},
|
|
153
|
+
stream: stream,
|
|
154
|
+
upload_size: upload_size,
|
|
155
|
+
chunk_size: chunk_size,
|
|
156
|
+
content_type: content_type,
|
|
157
|
+
timeout: timeout,
|
|
158
|
+
start_retry_policy: start_retry_policy,
|
|
159
|
+
control_plane_retry_policy: control_plane_retry_policy,
|
|
160
|
+
data_plane_retry_policy: data_plane_retry_policy,
|
|
161
|
+
on_progress: on_progress
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
##
|
|
167
|
+
# @private
|
|
168
|
+
# Immutable configuration for a run that resumes an existing upload session, i.e.
|
|
169
|
+
# {Gapic::ResumableUpload#resume}.
|
|
170
|
+
#
|
|
171
|
+
# Carries {COMMON_MEMBERS} plus the upload URL and chunk size the earlier run established. There is
|
|
172
|
+
# no `start_retry_policy` here: a resumed run issues no initiation request, so the member would
|
|
173
|
+
# always be dead.
|
|
174
|
+
#
|
|
175
|
+
# @!attribute [r] upload_url
|
|
176
|
+
# @return [String] Session upload URL returned by the upload backend
|
|
177
|
+
# @!attribute [r] chunk_size
|
|
178
|
+
# @return [Integer] Explicit chunk size in bytes (must be a positive integer). Server granularity is
|
|
179
|
+
# reported only during initiation, which a resumed run skips, so the size is carried forward from
|
|
180
|
+
# the earlier run rather than re-negotiated.
|
|
181
|
+
#
|
|
182
|
+
ResumeUploadConfig = Data.define(
|
|
183
|
+
*COMMON_MEMBERS,
|
|
184
|
+
:upload_url,
|
|
185
|
+
:chunk_size
|
|
186
|
+
) do
|
|
187
|
+
##
|
|
188
|
+
# @private
|
|
189
|
+
# Initializes a new upload resume configuration.
|
|
190
|
+
#
|
|
191
|
+
# @param upload_url [String] Session upload URL
|
|
192
|
+
# @param chunk_size [Integer] Explicit chunk size in bytes (must be a positive integer)
|
|
193
|
+
# @param stream [IO] Binary input stream to upload
|
|
194
|
+
# @param upload_size [Integer, nil] Total upload bytes if known upfront
|
|
195
|
+
# @param content_type [String, nil] MIME type of uploaded media
|
|
196
|
+
# @param timeout [Numeric, nil] Total upload timeout in seconds (zero/negative values treated as nil)
|
|
197
|
+
# @param control_plane_retry_policy [Gapic::Common::RetryPolicy, Hash, nil] Retry policy for control commands
|
|
198
|
+
# @param data_plane_retry_policy [Gapic::Common::RetryPolicy, Hash, nil] Retry policy for data commands
|
|
199
|
+
# @param on_progress [Proc, nil] Callback invoked as `->(progress)` with a {Progress} instance
|
|
200
|
+
# @raise [ArgumentError] If required arguments are missing or invalid
|
|
201
|
+
#
|
|
202
|
+
def initialize upload_url:,
|
|
203
|
+
chunk_size:,
|
|
204
|
+
stream:,
|
|
205
|
+
upload_size: nil,
|
|
206
|
+
content_type: nil,
|
|
207
|
+
timeout: nil,
|
|
208
|
+
control_plane_retry_policy: nil,
|
|
209
|
+
data_plane_retry_policy: nil,
|
|
210
|
+
on_progress: nil
|
|
211
|
+
raise ArgumentError, "upload_url is required" if upload_url.nil? || upload_url.to_s.strip.empty?
|
|
212
|
+
unless chunk_size.is_a?(Integer) && chunk_size.positive?
|
|
213
|
+
raise ArgumentError, "chunk_size must be a positive integer"
|
|
214
|
+
end
|
|
215
|
+
raise ArgumentError, "stream is required" if stream.nil?
|
|
216
|
+
|
|
217
|
+
super(
|
|
218
|
+
upload_url: upload_url,
|
|
219
|
+
chunk_size: chunk_size,
|
|
220
|
+
stream: stream,
|
|
221
|
+
upload_size: upload_size,
|
|
222
|
+
content_type: content_type,
|
|
223
|
+
timeout: timeout,
|
|
224
|
+
control_plane_retry_policy: control_plane_retry_policy,
|
|
225
|
+
data_plane_retry_policy: data_plane_retry_policy,
|
|
226
|
+
on_progress: on_progress
|
|
227
|
+
)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
##
|
|
232
|
+
# Immutable progress snapshot passed to the `on_progress` callback.
|
|
233
|
+
#
|
|
234
|
+
# The `on_progress` callback runs synchronously on the same thread as the upload protocol
|
|
235
|
+
# and must not block. Any exception raised inside the callback aborts the upload session
|
|
236
|
+
# and propagates out of {Gapic::ResumableUpload#start} or {Gapic::ResumableUpload#resume}.
|
|
237
|
+
#
|
|
238
|
+
# @!attribute [r] phase
|
|
239
|
+
# @return [Symbol] Current upload phase, one of {Progress::PHASES}
|
|
240
|
+
# @!attribute [r] bytes_uploaded
|
|
241
|
+
# @return [Integer] Cumulative bytes acknowledged by the server. Note that this is the
|
|
242
|
+
# server-confirmed offset and is not guaranteed to be monotonic — a server rewind during
|
|
243
|
+
# recovery can decrease this value.
|
|
244
|
+
# @!attribute [r] total_bytes
|
|
245
|
+
# @return [Integer, nil] Total upload size in bytes if known, or `nil`. Always set on the
|
|
246
|
+
# `:completed` phase — the total is known once the transfer finishes, even when `upload_size`
|
|
247
|
+
# was not supplied upfront.
|
|
248
|
+
#
|
|
249
|
+
Progress = Data.define(
|
|
250
|
+
:phase,
|
|
251
|
+
:bytes_uploaded,
|
|
252
|
+
:total_bytes
|
|
253
|
+
) do
|
|
254
|
+
##
|
|
255
|
+
# Initializes a new progress snapshot.
|
|
256
|
+
#
|
|
257
|
+
# @param phase [Symbol] Current upload phase, one of {Progress::PHASES}
|
|
258
|
+
# @param bytes_uploaded [Integer] Cumulative bytes acknowledged by the server
|
|
259
|
+
# @param total_bytes [Integer, nil] Total upload size in bytes if known, or nil
|
|
260
|
+
# @raise [ArgumentError] If the phase is not one of {Progress::PHASES}
|
|
261
|
+
#
|
|
262
|
+
def initialize phase:, bytes_uploaded:, total_bytes: nil
|
|
263
|
+
# Must use `self.class::` to access constants from the class scope
|
|
264
|
+
unless self.class::PHASES.include? phase
|
|
265
|
+
raise ArgumentError, "Invalid phase: #{phase.inspect}. Expected one of #{self.class::PHASES.inspect}"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
super(
|
|
269
|
+
phase: phase,
|
|
270
|
+
bytes_uploaded: bytes_uploaded,
|
|
271
|
+
total_bytes: total_bytes
|
|
272
|
+
)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
##
|
|
277
|
+
# Allowed lifecycle phases for an upload session.
|
|
278
|
+
#
|
|
279
|
+
# A callback observes `:initiating`, `:uploading`, `:recovering`, `:finalizing` and `:completed`.
|
|
280
|
+
# `:cancelling` is reserved: cancellation is not exposed on {Gapic::ResumableUpload}, so no phase
|
|
281
|
+
# with that value is currently emitted.
|
|
282
|
+
#
|
|
283
|
+
# @return [Array<Symbol>]
|
|
284
|
+
Progress::PHASES = [:initiating, :uploading, :recovering, :finalizing, :cancelling, :completed].freeze
|
|
285
|
+
|
|
286
|
+
##
|
|
287
|
+
# Immutable handle containing parameters necessary to resume an in-progress upload session.
|
|
288
|
+
# These parameters are provided by the server and can be persisted to resume the upload
|
|
289
|
+
# at a later time.
|
|
290
|
+
#
|
|
291
|
+
# @!attribute [r] upload_url
|
|
292
|
+
# @return [String] Upload session URL provided by the server
|
|
293
|
+
# @!attribute [r] chunk_size
|
|
294
|
+
# @return [Integer] Effective chunk size in bytes
|
|
295
|
+
#
|
|
296
|
+
ResumeHandle = Data.define(
|
|
297
|
+
:upload_url,
|
|
298
|
+
:chunk_size
|
|
299
|
+
) do
|
|
300
|
+
##
|
|
301
|
+
# Initializes a new resume handle.
|
|
302
|
+
#
|
|
303
|
+
# @param upload_url [String] Upload session URL provided by the server
|
|
304
|
+
# @param chunk_size [Integer] Effective chunk size in bytes
|
|
305
|
+
#
|
|
306
|
+
def initialize upload_url:, chunk_size:
|
|
307
|
+
super(
|
|
308
|
+
upload_url: upload_url,
|
|
309
|
+
chunk_size: chunk_size
|
|
310
|
+
)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
##
|
|
315
|
+
# @private
|
|
316
|
+
# Immutable state snapshot representing the current protocol progression.
|
|
317
|
+
#
|
|
318
|
+
# @!attribute [r] status
|
|
319
|
+
# @return [Symbol] Protocol lifecycle status, one of {Rules::STATUSES}
|
|
320
|
+
# @!attribute [r] upload_url
|
|
321
|
+
# @return [String, nil] Session upload URL returned by the upload backend
|
|
322
|
+
# @!attribute [r] offset
|
|
323
|
+
# @return [Integer] Contiguous bytes acknowledged by server
|
|
324
|
+
# @!attribute [r] chunk_size
|
|
325
|
+
# @return [Integer] Resolved effective chunk size in bytes
|
|
326
|
+
# @!attribute [r] chunk_granularity
|
|
327
|
+
# @return [Integer, nil] Alignment modulus returned by server
|
|
328
|
+
# @!attribute [r] in_flight_length
|
|
329
|
+
# @return [Integer] Byte length of in-flight chunk currently being transmitted
|
|
330
|
+
# @!attribute [r] last_error
|
|
331
|
+
# @return [StandardError, nil] Terminal exception if in an error or rejected status
|
|
332
|
+
# @!attribute [r] recovery_offset
|
|
333
|
+
# @return [Integer, nil] Server-confirmed offset when the open recovery episode began, or `nil` when no
|
|
334
|
+
# episode is open. A recovery episode is the run of consecutive recovery attempts during which the
|
|
335
|
+
# confirmed offset does not advance; every query in it after the first waits for the next backoff delay.
|
|
336
|
+
# See `design/resumable_upload/implementation-guide.md` section 6.2.1.
|
|
337
|
+
#
|
|
338
|
+
State = Data.define(
|
|
339
|
+
:status,
|
|
340
|
+
:upload_url,
|
|
341
|
+
:offset,
|
|
342
|
+
:chunk_size,
|
|
343
|
+
:chunk_granularity,
|
|
344
|
+
:in_flight_length,
|
|
345
|
+
:last_error,
|
|
346
|
+
:recovery_offset
|
|
347
|
+
) do
|
|
348
|
+
##
|
|
349
|
+
# @private
|
|
350
|
+
# Initializes a protocol state snapshot.
|
|
351
|
+
#
|
|
352
|
+
# @param status [Symbol] Protocol lifecycle status, one of {Rules::STATUSES}
|
|
353
|
+
# @param upload_url [String, nil] Session upload URL
|
|
354
|
+
# @param offset [Integer] Contiguous bytes acknowledged by server
|
|
355
|
+
# @param chunk_size [Integer] Resolved effective chunk size in bytes
|
|
356
|
+
# @param chunk_granularity [Integer, nil] Alignment modulus returned by server
|
|
357
|
+
# @param in_flight_length [Integer] Byte length of in-flight chunk
|
|
358
|
+
# @param last_error [StandardError, nil] Terminal exception
|
|
359
|
+
# @param recovery_offset [Integer, nil] Offset the open recovery episode began at; `nil` if none is open
|
|
360
|
+
#
|
|
361
|
+
def initialize status: :initializing,
|
|
362
|
+
upload_url: nil,
|
|
363
|
+
offset: 0,
|
|
364
|
+
chunk_size: Rules::DEFAULT_CHUNK_SIZE,
|
|
365
|
+
chunk_granularity: nil,
|
|
366
|
+
in_flight_length: 0,
|
|
367
|
+
last_error: nil,
|
|
368
|
+
recovery_offset: nil
|
|
369
|
+
super(
|
|
370
|
+
status: status,
|
|
371
|
+
upload_url: upload_url,
|
|
372
|
+
offset: offset,
|
|
373
|
+
chunk_size: chunk_size,
|
|
374
|
+
chunk_granularity: chunk_granularity,
|
|
375
|
+
in_flight_length: in_flight_length,
|
|
376
|
+
last_error: last_error,
|
|
377
|
+
recovery_offset: recovery_offset
|
|
378
|
+
)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
##
|
|
383
|
+
# @private
|
|
384
|
+
# Immutable decision snapshot emitted by Rules.decide.
|
|
385
|
+
#
|
|
386
|
+
# @!attribute [r] from_status
|
|
387
|
+
# @return [Symbol] The protocol status before the transition
|
|
388
|
+
# @!attribute [r] shape
|
|
389
|
+
# @return [Symbol] The canonical event shape
|
|
390
|
+
# @!attribute [r] recipe
|
|
391
|
+
# @return [Symbol] Selected transition recipe method name
|
|
392
|
+
# @!attribute [r] next_state
|
|
393
|
+
# @return [State] The new protocol state snapshot after transition
|
|
394
|
+
# @!attribute [r] instructions
|
|
395
|
+
# @return [Array<Object>] Emitted instructions for the Driver
|
|
396
|
+
#
|
|
397
|
+
Decision = Data.define(
|
|
398
|
+
:from_status,
|
|
399
|
+
:shape,
|
|
400
|
+
:recipe,
|
|
401
|
+
:next_state,
|
|
402
|
+
:instructions
|
|
403
|
+
) do
|
|
404
|
+
##
|
|
405
|
+
# @private
|
|
406
|
+
# Initializes a decision snapshot.
|
|
407
|
+
#
|
|
408
|
+
# @param from_status [Symbol] The protocol status before the transition
|
|
409
|
+
# @param shape [Symbol] The canonical event shape
|
|
410
|
+
# @param recipe [Symbol] Selected transition recipe method name
|
|
411
|
+
# @param next_state [State] Resulting protocol state snapshot
|
|
412
|
+
# @param instructions [Array<Object>] Emitted instructions for the Driver
|
|
413
|
+
#
|
|
414
|
+
def initialize from_status:, shape:, recipe:, next_state:, instructions: []
|
|
415
|
+
super(
|
|
416
|
+
from_status: from_status,
|
|
417
|
+
shape: shape,
|
|
418
|
+
recipe: recipe,
|
|
419
|
+
next_state: next_state,
|
|
420
|
+
instructions: instructions
|
|
421
|
+
)
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# rubocop:enable Metrics/ModuleLength
|