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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11f04f82fc6e98e5ca24d814d6162d68681999b6a7f2305cc32c77c597db2a88
4
- data.tar.gz: a36c758ffd65878ac9809c0ecfc515e2c6e0e817f5ad679e8574e5dbeb8a03f6
3
+ metadata.gz: f9cc2de9bcbe93a62a1706acc73f6d37064bbde7a81a5c9fd434570ed8c6e22f
4
+ data.tar.gz: 3be68b0522ce0e3465bfb555820c7c73882f2e1bf36ac009fd4bb4f31c0bb3bd
5
5
  SHA512:
6
- metadata.gz: 8fb7d4c578dc4b44383ac18e33159fd824465e63df13b10ed2d2fe142c4a9743876d7a3a41cd00680dc3e44cab88f6d3d6185198a8b74a85ace1ff30ee6d9e4e
7
- data.tar.gz: 3d508937a578e43a1fe8022557d08a7c01be477b9141a1d055c0d6a1df8e739eca0a9e2f14d178aa90c4b121d44dd12e437d73242250009916645948238af388
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 is currently a **preview** with no guarantees of stability or support. Please get
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
- ## Disclaimer
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.
@@ -14,6 +14,6 @@
14
14
 
15
15
  module Gapic
16
16
  module Common
17
- VERSION = "1.4.1".freeze
17
+ VERSION = "1.5.0".freeze
18
18
  end
19
19
  end
@@ -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}.
@@ -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
- body = body.to_s
290
+ body_str = body.to_s
291
291
  metadata = metadata.to_h rescue {}
292
- return if body.empty? && metadata.empty?
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", body
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|
@@ -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 = "An error has occurred when making a REST request: #{msg}" unless msg.nil?
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