gapic-common 0.0.1
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 +7 -0
- data/.yardopts +13 -0
- data/CHANGELOG.md +2 -0
- data/CODE_OF_CONDUCT.md +43 -0
- data/CONTRIBUTING.md +28 -0
- data/LICENSE +13 -0
- data/README.md +40 -0
- data/RELEASING.md +76 -0
- data/lib/gapic-common.rb +15 -0
- data/lib/gapic/call_options.rb +68 -0
- data/lib/gapic/call_options/retry_policy.rb +106 -0
- data/lib/gapic/common.rb +32 -0
- data/lib/gapic/common/version.rb +19 -0
- data/lib/gapic/config.rb +104 -0
- data/lib/gapic/config/method.rb +65 -0
- data/lib/gapic/grpc.rb +17 -0
- data/lib/gapic/grpc/service_stub.rb +158 -0
- data/lib/gapic/grpc/service_stub/rpc_call.rb +149 -0
- data/lib/gapic/grpc/status_details.rb +40 -0
- data/lib/gapic/headers.rb +43 -0
- data/lib/gapic/operation.rb +278 -0
- data/lib/gapic/operation/retry_policy.rb +92 -0
- data/lib/gapic/paged_enumerable.rb +260 -0
- data/lib/gapic/protobuf.rb +152 -0
- data/lib/gapic/stream_input.rb +76 -0
- metadata +261 -0
@@ -0,0 +1,260 @@
|
|
1
|
+
# Copyright 2019 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
|
+
##
|
17
|
+
# A class to provide the Enumerable interface to the response of a paginated method. PagedEnumerable assumes
|
18
|
+
# response message holds a list of resources and the token to the next page.
|
19
|
+
#
|
20
|
+
# PagedEnumerable provides the enumerations over the resource data, and also provides the enumerations over the
|
21
|
+
# pages themselves.
|
22
|
+
#
|
23
|
+
# @example normal iteration over resources.
|
24
|
+
# paged_enumerable.each { |resource| puts resource }
|
25
|
+
#
|
26
|
+
# @example per-page iteration.
|
27
|
+
# paged_enumerable.each_page { |page| puts page }
|
28
|
+
#
|
29
|
+
# @example Enumerable over pages.
|
30
|
+
# paged_enumerable.each_page do |page|
|
31
|
+
# page.each { |resource| puts resource }
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @example more exact operations over pages.
|
35
|
+
# while some_condition()
|
36
|
+
# page = paged_enumerable.page
|
37
|
+
# do_something(page)
|
38
|
+
# break if paged_enumerable.next_page?
|
39
|
+
# paged_enumerable.next_page
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
class PagedEnumerable
|
43
|
+
include Enumerable
|
44
|
+
|
45
|
+
##
|
46
|
+
# @attribute [r] page
|
47
|
+
# @return [Page] The current page object.
|
48
|
+
attr_reader :page
|
49
|
+
|
50
|
+
##
|
51
|
+
# @private
|
52
|
+
# @param grpc_stub [Gapic::GRPC::Stub] The Gapic gRPC stub object.
|
53
|
+
# @param method_name [Symbol] The RPC method name.
|
54
|
+
# @param request [Object] The request object.
|
55
|
+
# @param response [Object] The response object.
|
56
|
+
# @param operation [GRPC::ActiveCall::Operation] The RPC operation for the response.
|
57
|
+
# @param options [Gapic::CallOptions] The options for making the RPC call.
|
58
|
+
# @param format_resource [Proc] A Proc object to format the resource object. The Proc should accept response as an
|
59
|
+
# argument, and return a formatted resource object. Optional.
|
60
|
+
#
|
61
|
+
def initialize grpc_stub, method_name, request, response, operation, options, format_resource: nil
|
62
|
+
@grpc_stub = grpc_stub
|
63
|
+
@method_name = method_name
|
64
|
+
@request = request
|
65
|
+
@response = response
|
66
|
+
@options = options
|
67
|
+
@format_resource = format_resource
|
68
|
+
@resource_field = nil # will be set in verify_response!
|
69
|
+
|
70
|
+
verify_request!
|
71
|
+
verify_response!
|
72
|
+
|
73
|
+
@page = Page.new @response, @resource_field, operation, format_resource: @format_resource
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Iterate over the resources.
|
78
|
+
#
|
79
|
+
# @yield [Object] Gives the resource objects in the stream.
|
80
|
+
#
|
81
|
+
# @raise [RuntimeError] if it's not started yet.
|
82
|
+
#
|
83
|
+
def each
|
84
|
+
return enum_for :each unless block_given?
|
85
|
+
|
86
|
+
each_page do |page|
|
87
|
+
page.each do |obj|
|
88
|
+
yield obj
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Iterate over the pages.
|
95
|
+
#
|
96
|
+
# @yield [Page] Gives the pages in the stream.
|
97
|
+
#
|
98
|
+
# @raise if it's not started yet.
|
99
|
+
#
|
100
|
+
def each_page
|
101
|
+
return enum_for :each_page unless block_given?
|
102
|
+
|
103
|
+
loop do
|
104
|
+
break if @page.nil?
|
105
|
+
yield @page
|
106
|
+
next_page!
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# True if it has the next page.
|
112
|
+
#
|
113
|
+
def next_page?
|
114
|
+
@page.next_page_token?
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Update the response in the current page.
|
119
|
+
#
|
120
|
+
# @return [Page] the new page object.
|
121
|
+
#
|
122
|
+
def next_page!
|
123
|
+
unless next_page?
|
124
|
+
@page = nil
|
125
|
+
return @page
|
126
|
+
end
|
127
|
+
|
128
|
+
next_request = @request.dup
|
129
|
+
next_request.page_token = @page.next_page_token
|
130
|
+
@grpc_stub.call_rpc @method_name, next_request, options: @options do |next_response, next_operation|
|
131
|
+
@page = Page.new next_response, @resource_field, next_operation, format_resource: @format_resource
|
132
|
+
end
|
133
|
+
@page
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# The page token to be used for the next RPC call.
|
138
|
+
#
|
139
|
+
# @return [String]
|
140
|
+
#
|
141
|
+
def next_page_token
|
142
|
+
@page.next_page_token
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# The current response object, for the current page.
|
147
|
+
#
|
148
|
+
# @return [Object]
|
149
|
+
#
|
150
|
+
def response
|
151
|
+
@page.response
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def verify_request!
|
157
|
+
page_token = @request.class.descriptor.find do |f|
|
158
|
+
f.name == "page_token" && f.type == :string
|
159
|
+
end
|
160
|
+
raise ArgumentError, "#{@request.class} must have a page_token field (String)" if page_token.nil?
|
161
|
+
|
162
|
+
page_size = @request.class.descriptor.find do |f|
|
163
|
+
f.name == "page_size" && [:int32, :int64].include?(f.type)
|
164
|
+
end
|
165
|
+
return unless page_size.nil?
|
166
|
+
raise ArgumentError, "#{@request.class} must have a page_size field (Integer)"
|
167
|
+
end
|
168
|
+
|
169
|
+
def verify_response!
|
170
|
+
next_page_token = @response.class.descriptor.find do |f|
|
171
|
+
f.name == "next_page_token" && f.type == :string
|
172
|
+
end
|
173
|
+
raise ArgumentError, "#{@response.class} must have a next_page_token field (String)" if next_page_token.nil?
|
174
|
+
|
175
|
+
# Find all repeated FieldDescriptors on the response Descriptor
|
176
|
+
fields = @response.class.descriptor.select do |f|
|
177
|
+
f.label == :repeated && f.type == :message
|
178
|
+
end
|
179
|
+
|
180
|
+
repeated_field = fields.first
|
181
|
+
raise ArgumentError, "#{@response.class} must have one repeated field" if repeated_field.nil?
|
182
|
+
|
183
|
+
min_repeated_field_number = fields.map(&:number).min
|
184
|
+
if min_repeated_field_number != repeated_field.number
|
185
|
+
raise ArgumentError, "#{@response.class} must have one primary repeated field " \
|
186
|
+
"by both position and number"
|
187
|
+
end
|
188
|
+
|
189
|
+
# We have the correct repeated field, save the field's name
|
190
|
+
@resource_field = repeated_field.name
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# A class to represent a page in a PagedEnumerable. This also implements Enumerable, so it can iterate over the
|
195
|
+
# resource elements.
|
196
|
+
#
|
197
|
+
# @attribute [r] response
|
198
|
+
# @return [Object] the response object for the page.
|
199
|
+
# @attribute [r] operation
|
200
|
+
# @return [GRPC::ActiveCall::Operation] the RPC operation for the page.
|
201
|
+
class Page
|
202
|
+
include Enumerable
|
203
|
+
attr_reader :response, :operation
|
204
|
+
|
205
|
+
##
|
206
|
+
# @private
|
207
|
+
# @param response [Object] The response object for the page.
|
208
|
+
# @param resource_field [String] The name of the field in response which holds the resources.
|
209
|
+
# @param operation [GRPC::ActiveCall::Operation] the RPC operation for the page.
|
210
|
+
# @param format_resource [Proc] A Proc object to format the resource object. The Proc should accept response as an
|
211
|
+
# argument, and return a formatted resource object. Optional.
|
212
|
+
#
|
213
|
+
def initialize response, resource_field, operation, format_resource: nil
|
214
|
+
@response = response
|
215
|
+
@resource_field = resource_field
|
216
|
+
@operation = operation
|
217
|
+
@format_resource = format_resource
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Iterate over the resources.
|
222
|
+
#
|
223
|
+
# @yield [Object] Gives the resource objects in the page.
|
224
|
+
#
|
225
|
+
def each
|
226
|
+
return enum_for :each unless block_given?
|
227
|
+
|
228
|
+
return if @response.nil?
|
229
|
+
|
230
|
+
# We trust that the field exists and is an Enumerable
|
231
|
+
@response[@resource_field].each do |resource|
|
232
|
+
resource = @format_resource.call resource if @format_resource
|
233
|
+
yield resource
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
##
|
238
|
+
# The page token to be used for the next RPC call.
|
239
|
+
#
|
240
|
+
# @return [String]
|
241
|
+
#
|
242
|
+
def next_page_token
|
243
|
+
return if @response.nil?
|
244
|
+
|
245
|
+
@response.next_page_token
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# Truthiness of next_page_token.
|
250
|
+
#
|
251
|
+
# @return [Boolean]
|
252
|
+
#
|
253
|
+
def next_page_token?
|
254
|
+
return if @response.nil?
|
255
|
+
|
256
|
+
!@response.next_page_token.empty?
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# Copyright 2019 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 "google/protobuf/timestamp_pb"
|
16
|
+
|
17
|
+
module Gapic
|
18
|
+
##
|
19
|
+
# TODO: Describe Protobuf
|
20
|
+
module Protobuf
|
21
|
+
##
|
22
|
+
# Creates an instance of a protobuf message from a hash that may include nested hashes. `google/protobuf` allows
|
23
|
+
# for the instantiation of protobuf messages using hashes but does not allow for nested hashes to instantiate
|
24
|
+
# nested submessages.
|
25
|
+
#
|
26
|
+
# @param hash [Hash, Object] The hash to be converted into a proto message. If an instance of the proto message
|
27
|
+
# class is given, it is returned unchanged.
|
28
|
+
# @param to [Class] The corresponding protobuf message class of the given hash.
|
29
|
+
#
|
30
|
+
# @return [Object] An instance of the given message class.
|
31
|
+
def self.coerce hash, to:
|
32
|
+
return hash if hash.is_a? to
|
33
|
+
|
34
|
+
# Sanity check: input must be a Hash
|
35
|
+
raise ArgumentError, "Value #{hash} must be a Hash or a #{to.name}" unless hash.is_a? Hash
|
36
|
+
|
37
|
+
hash = coerce_submessages hash, to
|
38
|
+
to.new hash
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Coerces values of the given hash to be acceptable by the instantiation method provided by `google/protobuf`
|
43
|
+
#
|
44
|
+
# @private
|
45
|
+
#
|
46
|
+
# @param hash [Hash] The hash whose nested hashes will be coerced.
|
47
|
+
# @param message_class [Class] The corresponding protobuf message class of the given hash.
|
48
|
+
#
|
49
|
+
# @return [Hash] A hash whose nested hashes have been coerced.
|
50
|
+
def self.coerce_submessages hash, message_class
|
51
|
+
return nil if hash.nil?
|
52
|
+
coerced = {}
|
53
|
+
message_descriptor = message_class.descriptor
|
54
|
+
hash.each do |key, val|
|
55
|
+
field_descriptor = message_descriptor.lookup key.to_s
|
56
|
+
coerced[key] = if field_descriptor && field_descriptor.type == :message
|
57
|
+
coerce_submessage val, field_descriptor
|
58
|
+
elsif field_descriptor && field_descriptor.type == :bytes &&
|
59
|
+
(val.is_a?(IO) || val.is_a?(StringIO))
|
60
|
+
val.binmode.read
|
61
|
+
else
|
62
|
+
# `google/protobuf` should throw an error if no field descriptor is
|
63
|
+
# found. Simply pass through.
|
64
|
+
val
|
65
|
+
end
|
66
|
+
end
|
67
|
+
coerced
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Coerces the value of a field to be acceptable by the instantiation method of the wrapping message.
|
72
|
+
#
|
73
|
+
# @private
|
74
|
+
#
|
75
|
+
# @param val [Object] The value to be coerced.
|
76
|
+
# @param field_descriptor [Google::Protobuf::FieldDescriptor] The field descriptor of the value.
|
77
|
+
#
|
78
|
+
# @return [Object] The coerced version of the given value.
|
79
|
+
def self.coerce_submessage val, field_descriptor
|
80
|
+
if (field_descriptor.label == :repeated) && !(map_field? field_descriptor)
|
81
|
+
coerce_array val, field_descriptor
|
82
|
+
elsif field_descriptor.subtype.msgclass == Google::Protobuf::Timestamp && val.is_a?(Time)
|
83
|
+
time_to_timestamp val
|
84
|
+
else
|
85
|
+
coerce_value val, field_descriptor
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Coerces the values of an array to be acceptable by the instantiation method the wrapping message.
|
91
|
+
#
|
92
|
+
# @private
|
93
|
+
#
|
94
|
+
# @param array [Array<Object>] The values to be coerced.
|
95
|
+
# @param field_descriptor [Google::Protobuf::FieldDescriptor] The field descriptor of the values.
|
96
|
+
#
|
97
|
+
# @return [Array<Object>] The coerced version of the given values.
|
98
|
+
def self.coerce_array array, field_descriptor
|
99
|
+
raise ArgumentError, "Value " + array.to_s + " must be an array" unless array.is_a? Array
|
100
|
+
array.map do |val|
|
101
|
+
coerce_value val, field_descriptor
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Hack to determine if field_descriptor is for a map.
|
107
|
+
#
|
108
|
+
# TODO(geigerj): Remove this once protobuf Ruby supports an official way
|
109
|
+
# to determine if a FieldDescriptor represents a map.
|
110
|
+
# See: https://github.com/google/protobuf/issues/3425
|
111
|
+
def self.map_field? field_descriptor
|
112
|
+
(field_descriptor.label == :repeated) &&
|
113
|
+
(field_descriptor.subtype.name.include? "_MapEntry_")
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Coerces the value of a field to be acceptable by the instantiation method of the wrapping message.
|
118
|
+
#
|
119
|
+
# @private
|
120
|
+
#
|
121
|
+
# @param val [Object] The value to be coerced.
|
122
|
+
# @param field_descriptor [Google::Protobuf::FieldDescriptor] The field descriptor of the value.
|
123
|
+
#
|
124
|
+
# @return [Object] The coerced version of the given value.
|
125
|
+
def self.coerce_value val, field_descriptor
|
126
|
+
return val unless (val.is_a? Hash) && !(map_field? field_descriptor)
|
127
|
+
coerce val, to: field_descriptor.subtype.msgclass
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Utility for converting a Google::Protobuf::Timestamp instance to a Ruby time.
|
132
|
+
#
|
133
|
+
# @param timestamp [Google::Protobuf::Timestamp] The timestamp to be converted.
|
134
|
+
#
|
135
|
+
# @return [Time] The converted Time.
|
136
|
+
def self.timestamp_to_time timestamp
|
137
|
+
Time.at timestamp.nanos * 10**-9 + timestamp.seconds
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Utility for converting a Ruby Time instance to a Google::Protobuf::Timestamp.
|
142
|
+
#
|
143
|
+
# @param time [Time] The Time to be converted.
|
144
|
+
#
|
145
|
+
# @return [Google::Protobuf::Timestamp] The converted Google::Protobuf::Timestamp.
|
146
|
+
def self.time_to_timestamp time
|
147
|
+
Google::Protobuf::Timestamp.new seconds: time.to_i, nanos: time.nsec
|
148
|
+
end
|
149
|
+
|
150
|
+
private_class_method :coerce_submessages, :coerce_submessage, :coerce_array, :coerce_value, :map_field?
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Copyright 2019 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
|
+
##
|
17
|
+
# Manages requests for an input stream and holds the stream open until {#close} is called.
|
18
|
+
#
|
19
|
+
class StreamInput
|
20
|
+
##
|
21
|
+
# Create a new input stream object to manage streaming requests and hold the stream open until {#close} is called.
|
22
|
+
#
|
23
|
+
# @param requests [Object]
|
24
|
+
#
|
25
|
+
def initialize *requests
|
26
|
+
@queue = Queue.new
|
27
|
+
|
28
|
+
# Push initial requests into the queue
|
29
|
+
requests.each { |request| @queue.push request }
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Adds a request object to the stream.
|
34
|
+
#
|
35
|
+
# @param request [Object]
|
36
|
+
#
|
37
|
+
# @return [StreamInput] Returns self.
|
38
|
+
#
|
39
|
+
def push request
|
40
|
+
@queue.push request
|
41
|
+
|
42
|
+
self
|
43
|
+
end
|
44
|
+
alias << push
|
45
|
+
alias append push
|
46
|
+
|
47
|
+
##
|
48
|
+
# Closes the stream.
|
49
|
+
#
|
50
|
+
# @return [StreamInput] Returns self.
|
51
|
+
#
|
52
|
+
def close
|
53
|
+
@queue.push self
|
54
|
+
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# @private
|
60
|
+
# Iterates the requests given to the stream.
|
61
|
+
#
|
62
|
+
# @yield [request] The block for accessing each request.
|
63
|
+
# @yieldparam [Object] request The request object.
|
64
|
+
#
|
65
|
+
# @return [Enumerator] An Enumerator is returned if no block is given.
|
66
|
+
#
|
67
|
+
def to_enum
|
68
|
+
return enum_for :to_enum unless block_given?
|
69
|
+
loop do
|
70
|
+
request = @queue.pop
|
71
|
+
break if request.equal? self
|
72
|
+
yield request
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|