gapic-common 0.7.0 → 0.10.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: 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