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.
@@ -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