1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
|
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Gapic
##
# A class to provide the Enumerable interface to the response of a paginated method. PagedEnumerable assumes
# response message holds a list of resources and the token to the next page.
#
# PagedEnumerable provides the enumerations over the resource data, and also provides the enumerations over the
# pages themselves.
#
# @example normal iteration over resources.
# paged_enumerable.each { |resource| puts resource }
#
# @example per-page iteration.
# paged_enumerable.each_page { |page| puts page }
#
# @example Enumerable over pages.
# paged_enumerable.each_page do |page|
# page.each { |resource| puts resource }
# end
#
# @example more exact operations over pages.
# while some_condition()
# page = paged_enumerable.page
# do_something(page)
# break if paged_enumerable.next_page?
# paged_enumerable.next_page
# end
#
class PagedEnumerable
include Enumerable
##
# @attribute [r] page
# @return [Page] The current page object.
attr_reader :page
##
# @private
# @param grpc_stub [Gapic::GRPC::Stub] The Gapic gRPC stub object.
# @param method_name [Symbol] The RPC method name.
# @param request [Object] The request object.
# @param response [Object] The response object.
# @param operation [::GRPC::ActiveCall::Operation] The RPC operation for the response.
# @param options [Gapic::CallOptions] The options for making the RPC call.
# @param format_resource [Proc] A Proc object to format the resource object. The Proc should accept response as an
# argument, and return a formatted resource object. Optional.
#
def initialize grpc_stub, method_name, request, response, operation, options, format_resource: nil
@grpc_stub = grpc_stub
@method_name = method_name
@request = request
@response = response
@options = options
@format_resource = format_resource
@resource_field = nil # will be set in verify_response!
verify_request!
verify_response!
@page = Page.new @response, @resource_field, operation, format_resource: @format_resource
end
##
# Iterate over the resources.
#
# @yield [Object] Gives the resource objects in the stream.
#
# @raise [RuntimeError] if it's not started yet.
#
def each &block
return enum_for :each unless block_given?
each_page do |page|
page.each(&block)
end
end
##
# Iterate over the pages.
#
# @yield [Page] Gives the pages in the stream.
#
# @raise if it's not started yet.
#
def each_page
return enum_for :each_page unless block_given?
loop do
break if @page.nil?
yield @page
next_page!
end
end
##
# True if it has the next page.
#
def next_page?
@page.next_page_token?
end
##
# Update the response in the current page.
#
# @return [Page] the new page object.
#
def next_page!
unless next_page?
@page = nil
return @page
end
next_request = @request.dup
next_request.page_token = @page.next_page_token
@grpc_stub.call_rpc @method_name, next_request, options: @options do |next_response, next_operation|
@page = Page.new next_response, @resource_field, next_operation, format_resource: @format_resource
end
@page
end
alias next_page next_page!
##
# The page token to be used for the next RPC call.
#
# @return [String]
#
def next_page_token
@page.next_page_token
end
##
# The current response object, for the current page.
#
# @return [Object]
#
def response
@page.response
end
private
def verify_request!
page_token = @request.class.descriptor.find do |f|
f.name == "page_token" && f.type == :string
end
raise ArgumentError, "#{@request.class} must have a page_token field (String)" if page_token.nil?
page_size = @request.class.descriptor.find do |f|
f.name == "page_size" && [:int32, :int64].include?(f.type)
end
return unless page_size.nil?
raise ArgumentError, "#{@request.class} must have a page_size field (Integer)"
end
def verify_response!
next_page_token = @response.class.descriptor.find do |f|
f.name == "next_page_token" && f.type == :string
end
raise ArgumentError, "#{@response.class} must have a next_page_token field (String)" if next_page_token.nil?
# Find all repeated FieldDescriptors on the response Descriptor
fields = @response.class.descriptor.select do |f|
f.label == :repeated && f.type == :message
end
repeated_field = fields.first
raise ArgumentError, "#{@response.class} must have one repeated field" if repeated_field.nil?
min_repeated_field_number = fields.map(&:number).min
if min_repeated_field_number != repeated_field.number
raise ArgumentError, "#{@response.class} must have one primary repeated field by both position and number"
end
# We have the correct repeated field, save the field's name
@resource_field = repeated_field.name
end
##
# A class to represent a page in a PagedEnumerable. This also implements Enumerable, so it can iterate over the
# resource elements.
#
# @attribute [r] response
# @return [Object] the response object for the page.
# @attribute [r] operation
# @return [::GRPC::ActiveCall::Operation] the RPC operation for the page.
class Page
include Enumerable
attr_reader :response
attr_reader :operation
##
# @private
# @param response [Object] The response object for the page.
# @param resource_field [String] The name of the field in response which holds the resources.
# @param operation [::GRPC::ActiveCall::Operation] the RPC operation for the page.
# @param format_resource [Proc] A Proc object to format the resource object. The Proc should accept response as an
# argument, and return a formatted resource object. Optional.
#
def initialize response, resource_field, operation, format_resource: nil
@response = response
@resource_field = resource_field
@operation = operation
@format_resource = format_resource
end
##
# Iterate over the resources.
#
# @yield [Object] Gives the resource objects in the page.
#
def each
return enum_for :each unless block_given?
return if @response.nil?
# We trust that the field exists and is an Enumerable
@response[@resource_field].each do |resource|
resource = @format_resource.call resource if @format_resource
yield resource
end
end
##
# The page token to be used for the next RPC call.
#
# @return [String]
#
def next_page_token
return if @response.nil?
@response.next_page_token
end
##
# Truthiness of next_page_token.
#
# @return [Boolean]
#
def next_page_token?
return if @response.nil? # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
!@response.next_page_token.empty?
end
end
end
end
|