gapic-common 0.8.0 → 0.9.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: 5b3ff94cc009d187db3bbcaa3268adc302baf7918c4027da5b6b7eeb35156f9b
4
- data.tar.gz: 48ffb516c68d49a72203f76f99e5bebd6cdd9760d96520f754cb6f51e9b39d52
3
+ metadata.gz: 4187157fccf115d742dc9e5b074feccf9b65a5dc241bab8214810b4c257288a8
4
+ data.tar.gz: 7160ec37659fd5bbffc3e517bd40a65295da42fad7743b38869b58d59a34b09f
5
5
  SHA512:
6
- metadata.gz: f347fc6aae5fba58ab1fba7d090d34f116cbad71d02d8eaa7f7d6b498d830fb6fe0fdfc8fa80c2605a5077a194ee3b15c5ddb6d4d2be22f96f23c7e620afc36f
7
- data.tar.gz: dd712b166b3c4c2d5946fc4498fc36cc1758d236a987ff8f9efce7ccf8a8af75b07e447e35f29d98db7c00b42a2e67335ca84a9e55d29aa9606b48fb9a714f11
6
+ metadata.gz: 71cdc0f8c19c10a51dee060c3048b8b13b8223c0e527e103629058504b60013180e24232ae729014943e1d4b66ba98563796f35558d0d2b5fd955a49f0abf720
7
+ data.tar.gz: 31b53d85d36746f58b17e778f8fd4b5469a782ae9a68c3847eee309dcb5b2b4ae068785e295763ac17d081518656427408142426cfa7b0f128287599df3e2c47
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Release History
2
2
 
3
+ ### 0.9.0 (2022-05-18)
4
+
5
+ #### Features
6
+
7
+ * add full grpc transcoding to gapic-common
8
+ #### Bug Fixes
9
+
10
+ * small fixes for combined libraries and testing
11
+
3
12
  ### 0.8.0 / 2022-01-20
4
13
 
5
14
  * Add generic LROs helpers. These are used for the Nonstandard (not conforming to AIP-151) Cloud LROs.
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 [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.8.0".freeze
17
+ VERSION = "0.9.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"
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)
@@ -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! %r{/$}, ""
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
@@ -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.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.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.8.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-01-20 00:00:00.000000000 Z
11
+ date: 2022-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -270,6 +270,7 @@ files:
270
270
  - lib/gapic/call_options.rb
271
271
  - lib/gapic/call_options/retry_policy.rb
272
272
  - lib/gapic/common.rb
273
+ - lib/gapic/common/error.rb
273
274
  - lib/gapic/common/version.rb
274
275
  - lib/gapic/config.rb
275
276
  - lib/gapic/config/method.rb
@@ -288,6 +289,8 @@ files:
288
289
  - lib/gapic/rest/client_stub.rb
289
290
  - lib/gapic/rest/error.rb
290
291
  - lib/gapic/rest/faraday_middleware.rb
292
+ - lib/gapic/rest/grpc_transcoder.rb
293
+ - lib/gapic/rest/grpc_transcoder/http_binding.rb
291
294
  - lib/gapic/rest/operation.rb
292
295
  - lib/gapic/rest/paged_enumerable.rb
293
296
  - lib/gapic/stream_input.rb
@@ -310,7 +313,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
313
  - !ruby/object:Gem::Version
311
314
  version: '0'
312
315
  requirements: []
313
- rubygems_version: 3.1.2
316
+ rubygems_version: 3.3.5
314
317
  signing_key:
315
318
  specification_version: 4
316
319
  summary: Common code for GAPIC-generated API clients