gapic-common 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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