gapic-common 0.7.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb55a8128893e307e6f1237157b9456481ce7aa2e09a6dbeb19786359cf774ef
4
- data.tar.gz: 37b93b8e7b2d444679279d6f9e4b87b1ccfc460d4c9fc041ca390a22f115e4f5
3
+ metadata.gz: 5962314c78d1864ee6b768003c887d15af76619c8644a96392f0931ad6f2712c
4
+ data.tar.gz: 89920288c50070f089949eb11ff94010a52b69d1919b5e4ab6eb50cd9a3610ad
5
5
  SHA512:
6
- metadata.gz: b635e99c7a32cd3926e278312103197299a48679ea3d5ce6286ab7e5a5873cbb718106e3e69ac28b33316c5ff61c7b22fe67d5499b62298d1dc75c1f7f70eda6
7
- data.tar.gz: 81cefba38844981e6c137aff1d2de3fc5282e77b93c7761d20ae04007e7d46d07529b29fcf8315e98505268d101c500ad8e8d8e3df38fab5e0f4d8edbb914e4d
6
+ metadata.gz: 02c0113ca2287b79558c698393b3315b85c4359b15b90bf54929ea7e7f04a5189769559a69d22f35cebcedb99d62d1a02b2a7ca16739ea724565ace848a89279
7
+ data.tar.gz: cf43e6bedb320bc4f91f22e4b0354c87583b92e5734ea4d5d40d280bd9ffeaf0d272eda97e1e9bae2ce7e507e8a56a76c4a5c26ca5156326e7edd1bc88dfc5d9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Release History
2
2
 
3
+ ### 0.10.0 (2022-06-20)
4
+
5
+ #### Features
6
+
7
+ * Require at least Ruby 2.6
8
+ * Support faraday 2.0
9
+ #### Bug Fixes
10
+
11
+ * Fix precision issues in protobuf timestamp conversion
12
+ * Fix some Ruby 3.0 keyword argument usage errors
13
+
14
+ ### 0.9.0 (2022-05-18)
15
+
16
+ #### Features
17
+
18
+ * add full grpc transcoding to gapic-common
19
+ #### Bug Fixes
20
+
21
+ * small fixes for combined libraries and testing
22
+
23
+ ### 0.8.0 / 2022-01-20
24
+
25
+ * Add generic LROs helpers. These are used for the Nonstandard (not conforming to AIP-151) Cloud LROs.
26
+
3
27
  ### 0.7.0 / 2021-08-03
4
28
 
5
29
  * Require googleauth 0.17 for proper support of JWT credentials with custom scopes
data/README.md CHANGED
@@ -15,11 +15,11 @@ convenient and idiomatic API surface to callers.
15
15
 
16
16
  ## Supported Ruby Versions
17
17
 
18
- This library is supported on Ruby 2.4+.
18
+ This library is supported on Ruby 2.6+.
19
19
 
20
20
  Google provides official support for Ruby versions that are actively supported
21
21
  by Ruby Core—that is, Ruby versions that are either in normal maintenance or in
22
- security maintenance, and not end of life. Currently, this means Ruby 2.4 and
22
+ security maintenance, and not end of life. Currently, this means Ruby 2.6 and
23
23
  later. Older versions of Ruby _may_ still work, but are unsupported and not
24
24
  recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details
25
25
  about the Ruby support schedule.
@@ -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 [CONTRIBUTING](CONTRIBUTING.md) documentation for more information on how to get started.
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
@@ -14,6 +14,6 @@
14
14
 
15
15
  module Gapic
16
16
  module Common
17
- VERSION = "0.7.0".freeze
17
+ VERSION = "0.10.0".freeze
18
18
  end
19
19
  end
data/lib/gapic/common.rb CHANGED
@@ -15,6 +15,7 @@
15
15
  require "grpc/errors"
16
16
  require "grpc/core/status_codes"
17
17
 
18
+ require "gapic/common/error"
18
19
  require "gapic/call_options"
19
20
  require "gapic/headers"
20
21
  require "gapic/operation"
@@ -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}"]
@@ -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)
@@ -253,7 +253,7 @@ module Gapic
253
253
  # @yield operation [Gapic::Operation] Yields the finished Operation.
254
254
  #
255
255
  def wait_until_done! retry_policy: nil
256
- retry_policy = RetryPolicy.new retry_policy if retry_policy.is_a? Hash
256
+ retry_policy = RetryPolicy.new(**retry_policy) if retry_policy.is_a? Hash
257
257
  retry_policy ||= RetryPolicy.new
258
258
 
259
259
  until done?
@@ -181,8 +181,7 @@ module Gapic
181
181
 
182
182
  min_repeated_field_number = fields.map(&:number).min
183
183
  if min_repeated_field_number != repeated_field.number
184
- raise ArgumentError, "#{@response.class} must have one primary repeated field " \
185
- "by both position and number"
184
+ raise ArgumentError, "#{@response.class} must have one primary repeated field by both position and number"
186
185
  end
187
186
 
188
187
  # We have the correct repeated field, save the field's name
@@ -134,7 +134,7 @@ module Gapic
134
134
  #
135
135
  # @return [Time] The converted Time.
136
136
  def self.timestamp_to_time timestamp
137
- Time.at timestamp.nanos * 10**-9 + timestamp.seconds
137
+ Time.at timestamp.seconds, timestamp.nanos, :nanosecond
138
138
  end
139
139
 
140
140
  ##
@@ -14,6 +14,7 @@
14
14
 
15
15
  require "googleauth"
16
16
  require "gapic/rest/faraday_middleware"
17
+ require "faraday/retry"
17
18
 
18
19
  module Gapic
19
20
  module Rest
@@ -37,13 +38,13 @@ module Gapic
37
38
  def initialize endpoint:, credentials:
38
39
  @endpoint = endpoint
39
40
  @endpoint = "https://#{endpoint}" unless /^https?:/.match? endpoint
40
- @endpoint.sub! %r{/$}, ""
41
+ @endpoint = @endpoint.sub %r{/$}, ""
41
42
 
42
43
  @credentials = credentials
43
44
 
44
45
  @connection = Faraday.new url: @endpoint do |conn|
45
46
  conn.headers = { "Content-Type" => "application/json" }
46
- conn.request :google_authorization, @credentials
47
+ conn.request :google_authorization, @credentials unless @credentials.is_a? ::Symbol
47
48
  conn.request :retry
48
49
  conn.response :raise_error
49
50
  conn.adapter :net_http
@@ -115,9 +116,8 @@ module Gapic
115
116
  make_http_request :put, uri: uri, body: body, params: params, options: options
116
117
  end
117
118
 
118
- protected
119
-
120
119
  ##
120
+ # @private
121
121
  # Sends a http request via Faraday
122
122
  # @param verb [Symbol] http verb
123
123
  # @param uri [String] uri to send this request to
@@ -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 < StandardError
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.to_h 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
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..]
293
+ other_parts_pascal = other_parts.map(&:capitalize).join
294
+ "#{first_part}#{other_parts_pascal}"
295
+ end
296
+ end
297
+ end
298
+ end
@@ -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
- # A base class for the wrappers over the long-running operations to the response of a REST LRO method.
19
- #
20
- # @attribute [r] operation
21
- # @return [Object] The wrapped operation object.
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,56 +1,62 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gapic-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.10.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: 2021-08-05 00:00:00.000000000 Z
11
+ date: 2022-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: '1.3'
22
+ version: 3.a
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '1.3'
29
+ version: '1.9'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 3.a
27
33
  - !ruby/object:Gem::Dependency
28
- name: googleapis-common-protos
34
+ name: faraday-retry
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - ">="
32
38
  - !ruby/object:Gem::Version
33
- version: 1.3.11
39
+ version: '1.0'
34
40
  - - "<"
35
41
  - !ruby/object:Gem::Version
36
- version: 2.a
42
+ version: 3.a
37
43
  type: :runtime
38
44
  prerelease: false
39
45
  version_requirements: !ruby/object:Gem::Requirement
40
46
  requirements:
41
47
  - - ">="
42
48
  - !ruby/object:Gem::Version
43
- version: 1.3.11
49
+ version: '1.0'
44
50
  - - "<"
45
51
  - !ruby/object:Gem::Version
46
- version: 2.a
52
+ version: 3.a
47
53
  - !ruby/object:Gem::Dependency
48
- name: googleapis-common-protos-types
54
+ name: googleapis-common-protos
49
55
  requirement: !ruby/object:Gem::Requirement
50
56
  requirements:
51
57
  - - ">="
52
58
  - !ruby/object:Gem::Version
53
- version: 1.0.6
59
+ version: 1.3.12
54
60
  - - "<"
55
61
  - !ruby/object:Gem::Version
56
62
  version: 2.a
@@ -60,17 +66,17 @@ dependencies:
60
66
  requirements:
61
67
  - - ">="
62
68
  - !ruby/object:Gem::Version
63
- version: 1.0.6
69
+ version: 1.3.12
64
70
  - - "<"
65
71
  - !ruby/object:Gem::Version
66
72
  version: 2.a
67
73
  - !ruby/object:Gem::Dependency
68
- name: googleauth
74
+ name: googleapis-common-protos-types
69
75
  requirement: !ruby/object:Gem::Requirement
70
76
  requirements:
71
77
  - - ">="
72
78
  - !ruby/object:Gem::Version
73
- version: 0.17.0
79
+ version: 1.3.1
74
80
  - - "<"
75
81
  - !ruby/object:Gem::Version
76
82
  version: 2.a
@@ -80,10 +86,24 @@ dependencies:
80
86
  requirements:
81
87
  - - ">="
82
88
  - !ruby/object:Gem::Version
83
- version: 0.17.0
89
+ version: 1.3.1
84
90
  - - "<"
85
91
  - !ruby/object:Gem::Version
86
92
  version: 2.a
93
+ - !ruby/object:Gem::Dependency
94
+ name: googleauth
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '1.0'
100
+ type: :runtime
101
+ prerelease: false
102
+ version_requirements: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - "~>"
105
+ - !ruby/object:Gem::Version
106
+ version: '1.0'
87
107
  - !ruby/object:Gem::Dependency
88
108
  name: google-protobuf
89
109
  requirement: !ruby/object:Gem::Requirement
@@ -132,28 +152,28 @@ dependencies:
132
152
  requirements:
133
153
  - - "~>"
134
154
  - !ruby/object:Gem::Version
135
- version: 1.25.1
155
+ version: 1.26.0
136
156
  type: :development
137
157
  prerelease: false
138
158
  version_requirements: !ruby/object:Gem::Requirement
139
159
  requirements:
140
160
  - - "~>"
141
161
  - !ruby/object:Gem::Version
142
- version: 1.25.1
162
+ version: 1.26.0
143
163
  - !ruby/object:Gem::Dependency
144
164
  name: minitest
145
165
  requirement: !ruby/object:Gem::Requirement
146
166
  requirements:
147
167
  - - "~>"
148
168
  - !ruby/object:Gem::Version
149
- version: '5.14'
169
+ version: '5.16'
150
170
  type: :development
151
171
  prerelease: false
152
172
  version_requirements: !ruby/object:Gem::Requirement
153
173
  requirements:
154
174
  - - "~>"
155
175
  - !ruby/object:Gem::Version
156
- version: '5.14'
176
+ version: '5.16'
157
177
  - !ruby/object:Gem::Dependency
158
178
  name: minitest-autotest
159
179
  requirement: !ruby/object:Gem::Requirement
@@ -196,6 +216,20 @@ dependencies:
196
216
  - - "~>"
197
217
  - !ruby/object:Gem::Version
198
218
  version: '5.2'
219
+ - !ruby/object:Gem::Dependency
220
+ name: pry
221
+ requirement: !ruby/object:Gem::Requirement
222
+ requirements:
223
+ - - ">="
224
+ - !ruby/object:Gem::Version
225
+ version: '0.14'
226
+ type: :development
227
+ prerelease: false
228
+ version_requirements: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ">="
231
+ - !ruby/object:Gem::Version
232
+ version: '0.14'
199
233
  - !ruby/object:Gem::Dependency
200
234
  name: rake
201
235
  requirement: !ruby/object:Gem::Requirement
@@ -256,9 +290,12 @@ files:
256
290
  - lib/gapic/call_options.rb
257
291
  - lib/gapic/call_options/retry_policy.rb
258
292
  - lib/gapic/common.rb
293
+ - lib/gapic/common/error.rb
259
294
  - lib/gapic/common/version.rb
260
295
  - lib/gapic/config.rb
261
296
  - lib/gapic/config/method.rb
297
+ - lib/gapic/generic_lro/base_operation.rb
298
+ - lib/gapic/generic_lro/operation.rb
262
299
  - lib/gapic/grpc.rb
263
300
  - lib/gapic/grpc/service_stub.rb
264
301
  - lib/gapic/grpc/service_stub/rpc_call.rb
@@ -272,6 +309,8 @@ files:
272
309
  - lib/gapic/rest/client_stub.rb
273
310
  - lib/gapic/rest/error.rb
274
311
  - lib/gapic/rest/faraday_middleware.rb
312
+ - lib/gapic/rest/grpc_transcoder.rb
313
+ - lib/gapic/rest/grpc_transcoder/http_binding.rb
275
314
  - lib/gapic/rest/operation.rb
276
315
  - lib/gapic/rest/paged_enumerable.rb
277
316
  - lib/gapic/stream_input.rb
@@ -287,14 +326,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
287
326
  requirements:
288
327
  - - ">="
289
328
  - !ruby/object:Gem::Version
290
- version: '2.5'
329
+ version: '2.6'
291
330
  required_rubygems_version: !ruby/object:Gem::Requirement
292
331
  requirements:
293
332
  - - ">="
294
333
  - !ruby/object:Gem::Version
295
334
  version: '0'
296
335
  requirements: []
297
- rubygems_version: 3.1.6
336
+ rubygems_version: 3.3.14
298
337
  signing_key:
299
338
  specification_version: 4
300
339
  summary: Common code for GAPIC-generated API clients