gapic-common 0.6.1 → 0.9.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 -2
- data/README.md +1 -1
- data/lib/gapic/common/error.rb +23 -0
- data/lib/gapic/common/version.rb +1 -1
- data/lib/gapic/common.rb +1 -0
- data/lib/gapic/generic_lro/base_operation.rb +38 -0
- data/lib/gapic/generic_lro/operation.rb +325 -0
- data/lib/gapic/headers.rb +1 -1
- data/lib/gapic/operation.rb +1 -1
- data/lib/gapic/rest/client_stub.rb +3 -4
- data/lib/gapic/rest/error.rb +2 -1
- data/lib/gapic/rest/grpc_transcoder/http_binding.rb +66 -0
- data/lib/gapic/rest/grpc_transcoder.rb +298 -0
- data/lib/gapic/rest/operation.rb +6 -14
- data/lib/gapic/rest.rb +1 -0
- metadata +24 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4187157fccf115d742dc9e5b074feccf9b65a5dc241bab8214810b4c257288a8
|
4
|
+
data.tar.gz: 7160ec37659fd5bbffc3e517bd40a65295da42fad7743b38869b58d59a34b09f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71cdc0f8c19c10a51dee060c3048b8b13b8223c0e527e103629058504b60013180e24232ae729014943e1d4b66ba98563796f35558d0d2b5fd955a49f0abf720
|
7
|
+
data.tar.gz: 31b53d85d36746f58b17e778f8fd4b5469a782ae9a68c3847eee309dcb5b2b4ae068785e295763ac17d081518656427408142426cfa7b0f128287599df3e2c47
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,21 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
-
### 0.
|
3
|
+
### 0.9.0 (2022-05-18)
|
4
4
|
|
5
|
-
|
5
|
+
#### Features
|
6
|
+
|
7
|
+
* add full grpc transcoding to gapic-common
|
8
|
+
#### Bug Fixes
|
9
|
+
|
10
|
+
* small fixes for combined libraries and testing
|
11
|
+
|
12
|
+
### 0.8.0 / 2022-01-20
|
13
|
+
|
14
|
+
* Add generic LROs helpers. These are used for the Nonstandard (not conforming to AIP-151) Cloud LROs.
|
15
|
+
|
16
|
+
### 0.7.0 / 2021-08-03
|
17
|
+
|
18
|
+
* Require googleauth 0.17 for proper support of JWT credentials with custom scopes
|
6
19
|
|
7
20
|
### 0.6.0 / 2021-07-22
|
8
21
|
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ about the Ruby support schedule.
|
|
28
28
|
|
29
29
|
Contributions to this library are always welcome and highly encouraged.
|
30
30
|
|
31
|
-
See the
|
31
|
+
See the {file:CONTRIBUTING.md CONTRIBUTING} documentation for more information on how to get started.
|
32
32
|
|
33
33
|
## Versioning
|
34
34
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright 2022 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "json"
|
16
|
+
|
17
|
+
module Gapic
|
18
|
+
module Common
|
19
|
+
# Gapic Common exception class
|
20
|
+
class Error < StandardError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/gapic/common/version.rb
CHANGED
data/lib/gapic/common.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copyright 2021 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Gapic
|
16
|
+
module GenericLRO
|
17
|
+
##
|
18
|
+
# A base class for the wrappers over the long-running operations.
|
19
|
+
#
|
20
|
+
# @attribute [r] operation
|
21
|
+
# @return [Object] The wrapped operation object.
|
22
|
+
#
|
23
|
+
class BaseOperation
|
24
|
+
attr_reader :operation
|
25
|
+
|
26
|
+
##
|
27
|
+
# @private
|
28
|
+
# @param operation [Object] The operation object to be wrapped
|
29
|
+
def initialize operation
|
30
|
+
@operation = operation
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
attr_writer :operation
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
# Copyright 2021 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "gapic/generic_lro/base_operation"
|
16
|
+
require "gapic/operation/retry_policy"
|
17
|
+
|
18
|
+
module Gapic
|
19
|
+
module GenericLRO
|
20
|
+
##
|
21
|
+
# A class used to wrap the longrunning operation objects, including the nonstandard ones
|
22
|
+
# (`nonstandard` meaning not conforming to the AIP-151).
|
23
|
+
# It provides helper methods to poll and check for status of these operations.
|
24
|
+
#
|
25
|
+
class Operation < Gapic::GenericLRO::BaseOperation
|
26
|
+
##
|
27
|
+
# @param operation [Object] The long-running operation object that is returned by the initial method call.
|
28
|
+
#
|
29
|
+
# @param client [Object] The client that handles the polling for the longrunning operation.
|
30
|
+
#
|
31
|
+
# @param polling_method_name [String] The name of the methods on the client that polls the longrunning operation.
|
32
|
+
#
|
33
|
+
# @param operation_status_field [String] The name of the `status` field in the underlying long-running operation
|
34
|
+
# object. The `status` field signals that the operation has finished. It should either contain symbols, and
|
35
|
+
# be set to `:DONE` when finished or contain a boolean and be set to `true` when finished.
|
36
|
+
#
|
37
|
+
# @param request_values [Map<String, String>] The values that are to be copied from the request that
|
38
|
+
# triggered the longrunning operation, into the request that polls for the longrunning operation.
|
39
|
+
# The format is `name of the request field` -> `value`
|
40
|
+
#
|
41
|
+
# @param operation_name_field [String, nil] The name of the `name` field in the underlying long-running operation
|
42
|
+
# object. Optional.
|
43
|
+
#
|
44
|
+
# @param operation_err_field [String, nil] The name of the `error` field in the underlying long-running operation
|
45
|
+
# object. The `error` field should be a message-type, and have same semantics as `google.rpc.Status`, including
|
46
|
+
# an integer `code` subfield, that carries an error code. If the `operation_err_field` field is given,
|
47
|
+
# the `operation_err_code_field` and `operation_err_msg_field` parameters are ignored. Optional.
|
48
|
+
#
|
49
|
+
# @param operation_err_code_field [String, nil] The name of the `error_code` field in the underlying
|
50
|
+
# long-running operation object. It is ignored if `operation_err_field` is given. Optional.
|
51
|
+
#
|
52
|
+
# @param operation_err_msg_field [String, nil] The name of the `error_message` field in the underlying
|
53
|
+
# long-running operation object. It is ignored if `operation_err_field` is given. Optional.
|
54
|
+
#
|
55
|
+
# @param operation_copy_fields [Map<String, String>] The map of the fields that need to be copied from the
|
56
|
+
# long-running operation object that the polling method returns to the polling request.
|
57
|
+
# The format is `name of the operation object field` -> `name of the request field` (`from` -> `to`)
|
58
|
+
#
|
59
|
+
# @param options [Gapic::CallOptions] call options for this operation
|
60
|
+
#
|
61
|
+
def initialize operation, client:, polling_method_name:, operation_status_field:,
|
62
|
+
request_values: {}, operation_name_field: nil, operation_err_field: nil,
|
63
|
+
operation_err_code_field: nil, operation_err_msg_field: nil, operation_copy_fields: {},
|
64
|
+
options: {}
|
65
|
+
@client = client
|
66
|
+
@polling_method_name = polling_method_name
|
67
|
+
@operation_status_field = operation_status_field
|
68
|
+
|
69
|
+
@request_values = request_values || {}
|
70
|
+
|
71
|
+
@operation_name_field = operation_name_field
|
72
|
+
@operation_err_field = operation_err_field
|
73
|
+
@operation_err_code_field = operation_err_code_field
|
74
|
+
@operation_err_msg_field = operation_err_msg_field
|
75
|
+
|
76
|
+
@operation_copy_fields = operation_copy_fields || {}
|
77
|
+
|
78
|
+
@on_done_callbacks = []
|
79
|
+
@on_reload_callbacks = []
|
80
|
+
@options = options || {}
|
81
|
+
|
82
|
+
super operation
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# If the operation is done, returns the response. If the operation response is an error, the error will be
|
87
|
+
# returned. Otherwise returns nil.
|
88
|
+
#
|
89
|
+
# @return [Object, nil] The result of the operation or an error.
|
90
|
+
#
|
91
|
+
def results
|
92
|
+
return error if error?
|
93
|
+
return response if response?
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Returns the name of the operation, if specified.
|
98
|
+
#
|
99
|
+
# @return [String, nil] The name of the operation.
|
100
|
+
#
|
101
|
+
def name
|
102
|
+
return nil if @operation_name_field.nil?
|
103
|
+
operation.send @operation_name_field if operation.respond_to? @operation_name_field
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Checks if the operation is done. This does not send a new api call, but checks the result of the previous api
|
108
|
+
# call to see if done.
|
109
|
+
#
|
110
|
+
# @return [Boolean] Whether the operation is done.
|
111
|
+
#
|
112
|
+
def done?
|
113
|
+
return status if [true, false].include? status
|
114
|
+
|
115
|
+
status == :DONE
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Checks if the operation is done and the result is not an error. If the operation is not finished then this will
|
120
|
+
# return false.
|
121
|
+
#
|
122
|
+
# @return [Boolean] Whether a response has been returned.
|
123
|
+
#
|
124
|
+
def response?
|
125
|
+
done? && !error?
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# If the operation is completed successfully, returns the underlying operation object, otherwise returns nil.
|
130
|
+
#
|
131
|
+
# @return [Object, nil] The response of the operation.
|
132
|
+
def response
|
133
|
+
operation if response?
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Checks if the operation is done and the result is an error. If the operation is not finished then this will
|
138
|
+
# return false.
|
139
|
+
#
|
140
|
+
# @return [Boolean] Whether an error has been returned.
|
141
|
+
#
|
142
|
+
def error?
|
143
|
+
done? && (!err.nil? || !error_code.nil?)
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# If the operation response is an error, the error will be returned, otherwise returns nil.
|
148
|
+
#
|
149
|
+
# @return [Object, nil] The error object.
|
150
|
+
#
|
151
|
+
def error
|
152
|
+
return unless error?
|
153
|
+
err || GenericError.new(error_code, error_msg)
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Reloads the operation object.
|
158
|
+
#
|
159
|
+
# @param options [Gapic::CallOptions, Hash] The options for making the RPC call. A Hash can be provided
|
160
|
+
# to customize the options object, using keys that match the arguments for {Gapic::CallOptions.new}.
|
161
|
+
#
|
162
|
+
# @return [Gapic::GenericLRO::Operation] Since this method changes internal state, it returns itself.
|
163
|
+
#
|
164
|
+
def reload! options: nil
|
165
|
+
return self if done?
|
166
|
+
|
167
|
+
@on_reload_callbacks.each { |proc| proc.call self }
|
168
|
+
|
169
|
+
request_hash = @request_values.transform_keys(&:to_sym)
|
170
|
+
@operation_copy_fields.each do |field_from, field_to|
|
171
|
+
request_hash[field_to.to_sym] = operation.send field_from.to_s if operation.respond_to? field_from.to_s
|
172
|
+
end
|
173
|
+
|
174
|
+
options = merge_options options, @options
|
175
|
+
|
176
|
+
ops = @client.send @polling_method_name, request_hash, options
|
177
|
+
ops = ops.operation if ops.is_a? Gapic::GenericLRO::BaseOperation
|
178
|
+
|
179
|
+
self.operation = ops
|
180
|
+
|
181
|
+
if done?
|
182
|
+
@on_reload_callbacks.clear
|
183
|
+
@on_done_callbacks.each { |proc| proc.call self }
|
184
|
+
@on_done_callbacks.clear
|
185
|
+
end
|
186
|
+
|
187
|
+
self
|
188
|
+
end
|
189
|
+
alias refresh! reload!
|
190
|
+
|
191
|
+
##
|
192
|
+
# Blocking method to wait until the operation has completed or the maximum timeout has been reached. Upon
|
193
|
+
# completion, registered callbacks will be called, then - if a block is given - the block will be called.
|
194
|
+
#
|
195
|
+
# @param retry_policy [RetryPolicy, Hash, Proc] The policy for retry. A custom proc that takes the error as an
|
196
|
+
# argument and blocks can also be provided.
|
197
|
+
#
|
198
|
+
# @yield operation [Gapic::GenericLRO::Operation] Yields the finished Operation.
|
199
|
+
#
|
200
|
+
def wait_until_done! retry_policy: nil
|
201
|
+
retry_policy = ::Gapic::Operation::RetryPolicy.new retry_policy if retry_policy.is_a? Hash
|
202
|
+
retry_policy ||= ::Gapic::Operation::RetryPolicy.new
|
203
|
+
|
204
|
+
until done?
|
205
|
+
reload!
|
206
|
+
break unless retry_policy.call
|
207
|
+
end
|
208
|
+
|
209
|
+
yield self if block_given?
|
210
|
+
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Registers a callback to be run when an operation is being reloaded. If the operation has completed
|
216
|
+
# prior to a call to this function the callback will NOT be called or registered.
|
217
|
+
#
|
218
|
+
# @yield operation [Gapic::Operation] Yields the finished Operation.
|
219
|
+
#
|
220
|
+
def on_reload &block
|
221
|
+
return if done?
|
222
|
+
@on_reload_callbacks.push block
|
223
|
+
end
|
224
|
+
|
225
|
+
##
|
226
|
+
# Registers a callback to be run when a refreshed operation is marked as done. If the operation has completed
|
227
|
+
# prior to a call to this function the callback will be called instead of registered.
|
228
|
+
#
|
229
|
+
# @yield operation [Gapic::Operation] Yields the finished Operation.
|
230
|
+
#
|
231
|
+
def on_done &block
|
232
|
+
if done?
|
233
|
+
yield self
|
234
|
+
else
|
235
|
+
@on_done_callbacks.push block
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
##
|
242
|
+
# @return [String, Boolean, nil] A status, whether operation is Done,
|
243
|
+
# as either a boolean (`true` === Done) or a symbol (`:DONE` === Done)
|
244
|
+
#
|
245
|
+
def status
|
246
|
+
return nil if @operation_status_field.nil?
|
247
|
+
operation.send @operation_status_field
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# @return [String, nil] An error message if the error message field is specified
|
252
|
+
#
|
253
|
+
def err
|
254
|
+
return nil if @operation_err_field.nil?
|
255
|
+
operation.send @operation_err_field if operation.respond_to? @operation_err_field
|
256
|
+
end
|
257
|
+
|
258
|
+
##
|
259
|
+
# @return [String, nil] An error code if the error code field is specified
|
260
|
+
#
|
261
|
+
def error_code
|
262
|
+
return nil if @operation_err_code_field.nil?
|
263
|
+
operation.send @operation_err_code_field if operation.respond_to? @operation_err_code_field
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# @return [String, nil] An error message if the error message field is specified
|
268
|
+
#
|
269
|
+
def error_msg
|
270
|
+
return nil if @operation_err_msg_field.nil?
|
271
|
+
operation.send @operation_err_msg_field if operation.respond_to? @operation_err_msg_field
|
272
|
+
end
|
273
|
+
|
274
|
+
##
|
275
|
+
# Merges options given to the method with a baseline Gapic::Options object
|
276
|
+
#
|
277
|
+
# @param method_opts [Gapic::CallOptions, Hash] The options for making the RPC call given to a method invocation.
|
278
|
+
# A Hash can be provided to customize the options object, using keys that match the arguments
|
279
|
+
# for {Gapic::CallOptions.new}.
|
280
|
+
#
|
281
|
+
# @param baseline_opts [Gapic::CallOptions, Hash] The baseline options for making the RPC call.
|
282
|
+
# A Hash can be provided to customize the options object, using keys that match the arguments
|
283
|
+
# for {Gapic::CallOptions.new}.
|
284
|
+
#
|
285
|
+
def merge_options method_opts, baseline_opts
|
286
|
+
options = if method_opts.respond_to? :to_h
|
287
|
+
method_opts.to_h.merge baseline_opts.to_h
|
288
|
+
else
|
289
|
+
baseline_opts.to_h
|
290
|
+
end
|
291
|
+
|
292
|
+
Gapic::CallOptions.new(**options)
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# Represents a generic error that a generic LRO can report
|
297
|
+
#
|
298
|
+
# @!attribute [r] code
|
299
|
+
# @return [String] An error code
|
300
|
+
#
|
301
|
+
# @!attribute [r] message
|
302
|
+
# @return [String] An error message
|
303
|
+
#
|
304
|
+
class GenericError
|
305
|
+
attr_accessor :code
|
306
|
+
attr_accessor :message
|
307
|
+
|
308
|
+
##
|
309
|
+
# @param code [String] An error code
|
310
|
+
# @param message [String] An error message
|
311
|
+
def initialize code, message
|
312
|
+
@code = code
|
313
|
+
@message = message
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
protected
|
318
|
+
|
319
|
+
##
|
320
|
+
# @private
|
321
|
+
# @return [Object] The client that handles the polling for the longrunning operation.
|
322
|
+
attr_accessor :client
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
data/lib/gapic/headers.rb
CHANGED
@@ -38,7 +38,7 @@ module Gapic
|
|
38
38
|
|
39
39
|
ruby_version ||= ::RUBY_VERSION
|
40
40
|
gax_version ||= ::Gapic::Common::VERSION
|
41
|
-
grpc_version ||= ::GRPC::VERSION if defined? ::GRPC
|
41
|
+
grpc_version ||= ::GRPC::VERSION if defined? ::GRPC::VERSION
|
42
42
|
rest_version ||= ::Faraday::VERSION if defined? ::Faraday
|
43
43
|
|
44
44
|
x_goog_api_client_header = ["gl-ruby/#{ruby_version}"]
|
data/lib/gapic/operation.rb
CHANGED
@@ -64,7 +64,7 @@ module Gapic
|
|
64
64
|
#
|
65
65
|
# # Or block until the operation completes, passing a block to be called
|
66
66
|
# # on completion.
|
67
|
-
# op.wait_until_done do |operation|
|
67
|
+
# op.wait_until_done! do |operation|
|
68
68
|
# raise operation.results.message if operation.error?
|
69
69
|
# # process(operation.results)
|
70
70
|
# # process(operation.rmetadata)
|
@@ -37,13 +37,13 @@ module Gapic
|
|
37
37
|
def initialize endpoint:, credentials:
|
38
38
|
@endpoint = endpoint
|
39
39
|
@endpoint = "https://#{endpoint}" unless /^https?:/.match? endpoint
|
40
|
-
@endpoint.sub
|
40
|
+
@endpoint = @endpoint.sub %r{/$}, ""
|
41
41
|
|
42
42
|
@credentials = credentials
|
43
43
|
|
44
44
|
@connection = Faraday.new url: @endpoint do |conn|
|
45
45
|
conn.headers = { "Content-Type" => "application/json" }
|
46
|
-
conn.request :google_authorization, @credentials
|
46
|
+
conn.request :google_authorization, @credentials unless @credentials.is_a? ::Symbol
|
47
47
|
conn.request :retry
|
48
48
|
conn.response :raise_error
|
49
49
|
conn.adapter :net_http
|
@@ -115,9 +115,8 @@ module Gapic
|
|
115
115
|
make_http_request :put, uri: uri, body: body, params: params, options: options
|
116
116
|
end
|
117
117
|
|
118
|
-
protected
|
119
|
-
|
120
118
|
##
|
119
|
+
# @private
|
121
120
|
# Sends a http request via Faraday
|
122
121
|
# @param verb [Symbol] http verb
|
123
122
|
# @param uri [String] uri to send this request to
|
data/lib/gapic/rest/error.rb
CHANGED
@@ -13,11 +13,12 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
require "json"
|
16
|
+
require "gapic/common/error"
|
16
17
|
|
17
18
|
module Gapic
|
18
19
|
module Rest
|
19
20
|
# Gapic REST exception class
|
20
|
-
class Error <
|
21
|
+
class Error < ::Gapic::Common::Error
|
21
22
|
# @return [Integer] the http status code for the error
|
22
23
|
attr_reader :status_code
|
23
24
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright 2022 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Gapic
|
16
|
+
module Rest
|
17
|
+
class GrpcTranscoder
|
18
|
+
# @private
|
19
|
+
# A single binding for GRPC-REST transcoding of a request
|
20
|
+
# It includes a uri template with bound field parameters, a HTTP method type,
|
21
|
+
# and a body template
|
22
|
+
#
|
23
|
+
# @attribute [r] method
|
24
|
+
# @return [Symbol] The REST verb for the request.
|
25
|
+
# @attribute [r] template
|
26
|
+
# @return [String] The URI template for the request.
|
27
|
+
# @attribute [r] field_bindings
|
28
|
+
# @return [Array<FieldBinding>] The field bindings for the URI template variables.
|
29
|
+
# @attribute [r] body
|
30
|
+
# @return [String] The body template for the request.
|
31
|
+
class HttpBinding
|
32
|
+
attr_reader :method
|
33
|
+
attr_reader :template
|
34
|
+
attr_reader :field_bindings
|
35
|
+
attr_reader :body
|
36
|
+
|
37
|
+
def initialize method, template, field_bindings, body
|
38
|
+
@method = method
|
39
|
+
@template = template
|
40
|
+
@field_bindings = field_bindings
|
41
|
+
@body = body
|
42
|
+
end
|
43
|
+
|
44
|
+
# A single binding for a field of a request message.
|
45
|
+
# @attribute [r] field_path
|
46
|
+
# @return [String] The path of the bound field, e.g. `foo.bar`.
|
47
|
+
# @attribute [r] regex
|
48
|
+
# @return [Regexp] The regex to match on the bound field's string representation.
|
49
|
+
# @attribute [r] preserve_slashes
|
50
|
+
# @return [Boolean] Whether the slashes in the field value should be preserved
|
51
|
+
# (as opposed to percent-escaped)
|
52
|
+
class FieldBinding
|
53
|
+
attr_reader :field_path
|
54
|
+
attr_reader :regex
|
55
|
+
attr_reader :preserve_slashes
|
56
|
+
|
57
|
+
def initialize field_path, regex, preserve_slashes
|
58
|
+
@field_path = field_path
|
59
|
+
@regex = regex
|
60
|
+
@preserve_slashes = preserve_slashes
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
# Copyright 2022 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "gapic/rest/grpc_transcoder/http_binding"
|
16
|
+
|
17
|
+
module Gapic
|
18
|
+
module Rest
|
19
|
+
# @private
|
20
|
+
# Transcodes a proto request message into HTTP Rest call components
|
21
|
+
# using a configuration of bindings.
|
22
|
+
# Internal doc go/actools-regapic-grpc-transcoding.
|
23
|
+
class GrpcTranscoder
|
24
|
+
def initialize bindings = nil
|
25
|
+
@bindings = bindings || []
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @private
|
30
|
+
# Creates a new trascoder that is a copy of this one, but with an additional
|
31
|
+
# binding defined by the parameters.
|
32
|
+
#
|
33
|
+
# @param uri_method [Symbol] The rest verb for the binding.
|
34
|
+
# @param uri_template [String] The string with uri template for the binding.
|
35
|
+
# This string will be expanded with the parameters from variable bindings.
|
36
|
+
# @param matches [Array<Array>] Variable bindings in an array. Every element
|
37
|
+
# of the array is an [Array] triplet, where:
|
38
|
+
# - the first element is a [String] field path (e.g. `foo.bar`) in the request
|
39
|
+
# to bind to
|
40
|
+
# - the second element is a [Regexp] to match the field value
|
41
|
+
# - the third element is a [Boolean] whether the slashes in the field value
|
42
|
+
# should be preserved (as opposed to escaped) when expanding the uri template.
|
43
|
+
# @param body [String, Nil] The body template, e.g. `*` or a field path.
|
44
|
+
#
|
45
|
+
# @return [Gapic::Rest::GrpcTranscoder] The updated transcoder.
|
46
|
+
def with_bindings uri_method:, uri_template:, matches: [], body: nil
|
47
|
+
template = uri_template
|
48
|
+
|
49
|
+
matches.each do |name, _regex, _preserve_slashes|
|
50
|
+
unless uri_template =~ /({#{Regexp.quote name}})/
|
51
|
+
err_msg = "Binding configuration is incorrect: missing parameter in the URI template.\n" \
|
52
|
+
"Parameter `#{name}` is specified for matching but there is no corresponding parameter" \
|
53
|
+
" `{#{name}}` in the URI template."
|
54
|
+
raise ::Gapic::Common::Error, err_msg
|
55
|
+
end
|
56
|
+
|
57
|
+
template = template.gsub "{#{name}}", ""
|
58
|
+
end
|
59
|
+
|
60
|
+
if template =~ /{([a-zA-Z_.]+)}/
|
61
|
+
err_name = Regexp.last_match[1]
|
62
|
+
err_msg = "Binding configuration is incorrect: missing match configuration.\n" \
|
63
|
+
"Parameter `{#{err_name}}` is specified in the URI template but there is no" \
|
64
|
+
" corresponding match configuration for `#{err_name}`."
|
65
|
+
raise ::Gapic::Common::Error, err_msg
|
66
|
+
end
|
67
|
+
|
68
|
+
if body&.include? "."
|
69
|
+
raise ::Gapic::Common::Error,
|
70
|
+
"Provided body template `#{body}` points to a field in a sub-message. This is not supported."
|
71
|
+
end
|
72
|
+
|
73
|
+
field_bindings = matches.map do |name, regex, preserve_slashes|
|
74
|
+
HttpBinding::FieldBinding.new name, regex, preserve_slashes
|
75
|
+
end
|
76
|
+
GrpcTranscoder.new @bindings + [HttpBinding.new(uri_method, uri_template, field_bindings, body)]
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# @private
|
81
|
+
# Performs the full grpc transcoding -- creating a REST request from the GRPC request
|
82
|
+
# by matching the http bindings and choosing the last one to match.
|
83
|
+
# From the matching binding and the request the following components of the REST request
|
84
|
+
# are produced:
|
85
|
+
# - A [Symbol] representing the Rest verb (e.g. `:get`)
|
86
|
+
# - Uri [String] (e.g. `books/100:read`)
|
87
|
+
# - Query string params in the form of key-value pairs [Array<Array{String, String}>]
|
88
|
+
# (e.g. [["foo", "bar"], ["baz", "qux"]])
|
89
|
+
# - Body of the request [String]
|
90
|
+
#
|
91
|
+
# @param request [Object] The GRPC request object
|
92
|
+
#
|
93
|
+
# @return [Array] The components of the transcoded request.
|
94
|
+
def transcode request
|
95
|
+
# Using bindings in reverse here because of the "last one wins" rule
|
96
|
+
@bindings.reverse.each do |http_binding|
|
97
|
+
# The main reason we are using request.to_json here
|
98
|
+
# is that the unset proto3_optional fields will not be
|
99
|
+
# in that JSON, letting us skip the checks that would look like
|
100
|
+
# `request.respond_to?("has_#{key}?".to_sym) && !request.send("has_#{key}?".to_sym)`
|
101
|
+
# The reason we set emit_defaults: true is to avoid
|
102
|
+
# having to figure out default values for the required
|
103
|
+
# fields at a runtime.
|
104
|
+
#
|
105
|
+
# Make a new one for each binding because extract_scalar_value! is destructive
|
106
|
+
request_hash = JSON.parse request.to_json emit_defaults: true
|
107
|
+
|
108
|
+
uri_values = bind_uri_values! http_binding, request_hash
|
109
|
+
next if uri_values.any? { |_, value| value.nil? }
|
110
|
+
|
111
|
+
if http_binding.body && http_binding.body != "*"
|
112
|
+
# Note that the body template can only point to a top-level field,
|
113
|
+
# so there is no need to split the path.
|
114
|
+
body_binding_camel = camel_name_for http_binding.body
|
115
|
+
next unless request_hash.key? body_binding_camel
|
116
|
+
end
|
117
|
+
|
118
|
+
method = http_binding.method
|
119
|
+
uri = expand_template http_binding.template, uri_values
|
120
|
+
body, query_params = construct_body_query_params http_binding.body, request_hash, request
|
121
|
+
|
122
|
+
return method, uri, query_params, body
|
123
|
+
end
|
124
|
+
|
125
|
+
raise ::Gapic::Common::Error,
|
126
|
+
"Request object does not match any transcoding template. Cannot form a correct REST call."
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# Binds request values for the uri template expansion.
|
132
|
+
# This method modifies the provided `request_hash` parameter.
|
133
|
+
# Returned values are percent-escaped with slashes potentially preserved.
|
134
|
+
# @param http_binding [Gapic::Rest::GrpcTranscoder::HttpBinding]
|
135
|
+
# Http binding to get the field bindings from.
|
136
|
+
# @param request_hash [Hash]
|
137
|
+
# A hash of the GRPC request with the unset proto3_optional fields pre-removed.
|
138
|
+
# !!! This hash will be modified. The bound fields will be deleted. !!!
|
139
|
+
# @return [Hash{String, String}]
|
140
|
+
# Name to value hash of the variables for the uri template expansion.
|
141
|
+
# The values are percent-escaped with slashes potentially preserved.
|
142
|
+
def bind_uri_values! http_binding, request_hash
|
143
|
+
http_binding.field_bindings.map do |field_binding|
|
144
|
+
field_path_camel = field_binding.field_path.split(".").map { |part| camel_name_for part }.join(".")
|
145
|
+
field_value = extract_scalar_value! request_hash, field_path_camel, field_binding.regex
|
146
|
+
|
147
|
+
if field_value
|
148
|
+
field_value = if field_binding.preserve_slashes
|
149
|
+
field_value.split("/").map { |segment| percent_escape(segment) }.join("/")
|
150
|
+
else
|
151
|
+
percent_escape field_value
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
[field_binding.field_path, field_value]
|
156
|
+
end.to_h
|
157
|
+
end
|
158
|
+
|
159
|
+
# Percent-escapes a string.
|
160
|
+
# @param str [String] String to escape.
|
161
|
+
# @return str [String] Escaped string.
|
162
|
+
def percent_escape str
|
163
|
+
# `+` to represent spaces is not currently supported in Showcase server.
|
164
|
+
CGI.escape(str).gsub("+", "%20")
|
165
|
+
end
|
166
|
+
|
167
|
+
# Constructs body and query parameters for the Rest request.
|
168
|
+
# @param body_template [String, Nil] The template for the body, e.g. `*`.
|
169
|
+
# @param request_hash_without_uri [Hash]
|
170
|
+
# The hash of the GRPC request with the unset proto3_optional fields
|
171
|
+
# and the values that are bound to URI removed.
|
172
|
+
# @param request [Object] The GRPC request.
|
173
|
+
# @return [Array{String, Array}] A pair of body and query parameters.
|
174
|
+
def construct_body_query_params body_template, request_hash_without_uri, request
|
175
|
+
body = ""
|
176
|
+
query_params = []
|
177
|
+
|
178
|
+
if body_template == "*"
|
179
|
+
body = request_hash_without_uri.to_json
|
180
|
+
elsif body_template && body_template != ""
|
181
|
+
# Using a `request` here instead of `request_hash_without_uri`
|
182
|
+
# because if `body` is bound to a message field,
|
183
|
+
# the fields of the corresponding sub-message,
|
184
|
+
# which were used when constructing the URI, should not be deleted
|
185
|
+
# (as opposed to the case when `body` is `*`).
|
186
|
+
#
|
187
|
+
# The `request_hash_without_uri` at this point was mutated to delete these fields.
|
188
|
+
#
|
189
|
+
# Note that the body template can only point to a top-level field
|
190
|
+
request_hash_without_uri.delete camel_name_for body_template
|
191
|
+
body = request.send(body_template.to_sym).to_json(emit_defaults: true)
|
192
|
+
query_params = build_query_params request_hash_without_uri
|
193
|
+
else
|
194
|
+
query_params = build_query_params request_hash_without_uri
|
195
|
+
end
|
196
|
+
|
197
|
+
[body, query_params]
|
198
|
+
end
|
199
|
+
|
200
|
+
# Builds query params for the REST request.
|
201
|
+
# This function calls itself recursively for every submessage field, passing
|
202
|
+
# the submessage hash as request and the path to the submessage field as a prefix.
|
203
|
+
# @param request_hash [Hash]
|
204
|
+
# A hash of the GRPC request or the sub-request with the unset
|
205
|
+
# proto3_optional fields and the values that are bound to URI removed.
|
206
|
+
# @param prefix [String] A prefix to form the correct query parameter key.
|
207
|
+
# @return [Array{String, String}] Query string params as key-value pairs.
|
208
|
+
def build_query_params request_hash, prefix = ""
|
209
|
+
result = []
|
210
|
+
request_hash.each do |key, value|
|
211
|
+
full_key_name = "#{prefix}#{key}"
|
212
|
+
case value
|
213
|
+
when ::Array
|
214
|
+
value.each do |_val|
|
215
|
+
result.push "#{full_key_name}=#{value}"
|
216
|
+
end
|
217
|
+
when ::Hash
|
218
|
+
result += build_query_params value, "#{full_key_name}."
|
219
|
+
else
|
220
|
+
result.push "#{full_key_name}=#{value}" unless value.nil?
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
result
|
225
|
+
end
|
226
|
+
|
227
|
+
# Extracts a non-submessage non-array value from the request hash by path
|
228
|
+
# if its string representation matches the regex provided.
|
229
|
+
# This method modifies the provided `request_hash` parameter.
|
230
|
+
# Returns nil if:
|
231
|
+
# - the field is not found
|
232
|
+
# - the field is a Message or an array,
|
233
|
+
# - the regex does not match
|
234
|
+
# @param request_hash [Hash]
|
235
|
+
# A hash of the GRPC request or the sub-request with the unset
|
236
|
+
# proto3_optional fields removed.
|
237
|
+
# !!! This hash will be modified. The extracted field will be deleted. !!!
|
238
|
+
# @param field_path [String] A path to the field, e.g. `foo.bar`.
|
239
|
+
# @param regex [Regexp] A regex to match on the field's string representation.
|
240
|
+
# @return [String, Nil] the field's string representation or nil.
|
241
|
+
def extract_scalar_value! request_hash, field_path, regex
|
242
|
+
parent, name = find_value request_hash, field_path
|
243
|
+
value = parent.delete name
|
244
|
+
|
245
|
+
# Covers the case where in `foo.bar.baz`, `baz` is still a submessage or an array.
|
246
|
+
return nil if value.is_a?(::Hash) || value.is_a?(::Array)
|
247
|
+
return value.to_s if value.to_s =~ regex
|
248
|
+
end
|
249
|
+
|
250
|
+
# Finds a value in the hash by path.
|
251
|
+
# @param request_hash [Hash] A hash of the GRPC request or the sub-request.
|
252
|
+
# @param field_path [String] A path of the field, e.g. `foo.bar`.
|
253
|
+
def find_value request_hash, field_path
|
254
|
+
path_split = field_path.split "."
|
255
|
+
|
256
|
+
value_parent = nil
|
257
|
+
value = request_hash
|
258
|
+
last_field_name = nil
|
259
|
+
path_split.each do |curr_field|
|
260
|
+
# Covers the case when in `foo.bar.baz`, `bar` is not a submessage field
|
261
|
+
# or is a submessage field initialized with nil.
|
262
|
+
return {}, nil unless value.is_a? ::Hash
|
263
|
+
value_parent = value
|
264
|
+
last_field_name = curr_field
|
265
|
+
value = value[curr_field]
|
266
|
+
end
|
267
|
+
|
268
|
+
[value_parent, last_field_name]
|
269
|
+
end
|
270
|
+
|
271
|
+
# Performs variable expansion on the template using the bindings provided
|
272
|
+
# @param template [String] The Uri template.
|
273
|
+
# @param bindings [Hash{String, String}]
|
274
|
+
# The variable bindings. The values should be percent-escaped
|
275
|
+
# (with slashes potentially preserved).
|
276
|
+
# @return [String] The expanded template.
|
277
|
+
def expand_template template, bindings
|
278
|
+
result = template
|
279
|
+
bindings.each do |name, value|
|
280
|
+
result = result.gsub "{#{name}}", value
|
281
|
+
end
|
282
|
+
result
|
283
|
+
end
|
284
|
+
|
285
|
+
##
|
286
|
+
# Converts a snake_case parameter name into camelCase for query string parameters.
|
287
|
+
# @param attr_name [String] Parameter name.
|
288
|
+
# @return [String] Camel-cased parameter name.
|
289
|
+
def camel_name_for attr_name
|
290
|
+
parts = attr_name.split "_"
|
291
|
+
first_part = parts[0]
|
292
|
+
other_parts = parts[1..-1]
|
293
|
+
other_parts_pascal = other_parts.map(&:capitalize).join
|
294
|
+
"#{first_part}#{other_parts_pascal}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
data/lib/gapic/rest/operation.rb
CHANGED
@@ -12,22 +12,14 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
require "gapic/generic_lro/base_operation"
|
16
|
+
|
15
17
|
module Gapic
|
16
18
|
module Rest
|
17
19
|
##
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# @
|
21
|
-
|
22
|
-
class BaseOperation
|
23
|
-
attr_reader :operation
|
24
|
-
|
25
|
-
##
|
26
|
-
# @private
|
27
|
-
# @param operation [Object] The wrapped operation object.
|
28
|
-
def initialize operation
|
29
|
-
@operation = operation
|
30
|
-
end
|
31
|
-
end
|
20
|
+
# This alias is left here for the backwards compatibility purposes.
|
21
|
+
# Rest LROs now use the same GenericLRO base as the GRPC LROs.
|
22
|
+
# @deprecated Use {Gapic::GenericLRO::BaseOperation} instead.
|
23
|
+
BaseOperation = Gapic::GenericLRO::BaseOperation
|
32
24
|
end
|
33
25
|
end
|
data/lib/gapic/rest.rb
CHANGED
@@ -24,6 +24,7 @@ require "gapic/protobuf"
|
|
24
24
|
require "gapic/rest/client_stub"
|
25
25
|
require "gapic/rest/error"
|
26
26
|
require "gapic/rest/faraday_middleware"
|
27
|
+
require "gapic/rest/grpc_transcoder"
|
27
28
|
require "gapic/rest/operation"
|
28
29
|
require "gapic/rest/paged_enumerable"
|
29
30
|
require "json"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gapic-common
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Google API Authors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -70,7 +70,7 @@ dependencies:
|
|
70
70
|
requirements:
|
71
71
|
- - ">="
|
72
72
|
- !ruby/object:Gem::Version
|
73
|
-
version: 0.
|
73
|
+
version: 0.17.0
|
74
74
|
- - "<"
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: 2.a
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
requirements:
|
81
81
|
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: 0.
|
83
|
+
version: 0.17.0
|
84
84
|
- - "<"
|
85
85
|
- !ruby/object:Gem::Version
|
86
86
|
version: 2.a
|
@@ -196,6 +196,20 @@ dependencies:
|
|
196
196
|
- - "~>"
|
197
197
|
- !ruby/object:Gem::Version
|
198
198
|
version: '5.2'
|
199
|
+
- !ruby/object:Gem::Dependency
|
200
|
+
name: pry
|
201
|
+
requirement: !ruby/object:Gem::Requirement
|
202
|
+
requirements:
|
203
|
+
- - ">="
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0.14'
|
206
|
+
type: :development
|
207
|
+
prerelease: false
|
208
|
+
version_requirements: !ruby/object:Gem::Requirement
|
209
|
+
requirements:
|
210
|
+
- - ">="
|
211
|
+
- !ruby/object:Gem::Version
|
212
|
+
version: '0.14'
|
199
213
|
- !ruby/object:Gem::Dependency
|
200
214
|
name: rake
|
201
215
|
requirement: !ruby/object:Gem::Requirement
|
@@ -256,9 +270,12 @@ files:
|
|
256
270
|
- lib/gapic/call_options.rb
|
257
271
|
- lib/gapic/call_options/retry_policy.rb
|
258
272
|
- lib/gapic/common.rb
|
273
|
+
- lib/gapic/common/error.rb
|
259
274
|
- lib/gapic/common/version.rb
|
260
275
|
- lib/gapic/config.rb
|
261
276
|
- lib/gapic/config/method.rb
|
277
|
+
- lib/gapic/generic_lro/base_operation.rb
|
278
|
+
- lib/gapic/generic_lro/operation.rb
|
262
279
|
- lib/gapic/grpc.rb
|
263
280
|
- lib/gapic/grpc/service_stub.rb
|
264
281
|
- lib/gapic/grpc/service_stub/rpc_call.rb
|
@@ -272,6 +289,8 @@ files:
|
|
272
289
|
- lib/gapic/rest/client_stub.rb
|
273
290
|
- lib/gapic/rest/error.rb
|
274
291
|
- lib/gapic/rest/faraday_middleware.rb
|
292
|
+
- lib/gapic/rest/grpc_transcoder.rb
|
293
|
+
- lib/gapic/rest/grpc_transcoder/http_binding.rb
|
275
294
|
- lib/gapic/rest/operation.rb
|
276
295
|
- lib/gapic/rest/paged_enumerable.rb
|
277
296
|
- lib/gapic/stream_input.rb
|
@@ -294,7 +313,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
294
313
|
- !ruby/object:Gem::Version
|
295
314
|
version: '0'
|
296
315
|
requirements: []
|
297
|
-
rubygems_version: 3.
|
316
|
+
rubygems_version: 3.3.5
|
298
317
|
signing_key:
|
299
318
|
specification_version: 4
|
300
319
|
summary: Common code for GAPIC-generated API clients
|