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
|
# frozen_string_literal: true
module Aws
# Decorates a {Seahorse::Client::Response} with paging convenience methods.
# Some AWS calls provide paged responses to limit the amount of data returned
# with each response. To optimize for latency, some APIs may return an
# inconsistent number of responses per page. You should rely on the values of
# the `next_page?` method or using enumerable methods such as `each` rather
# than the number of items returned to iterate through results. See below for
# examples.
#
# @note Methods such as `to_json` will enumerate all of the responses before
# returning the full response as JSON.
#
# # Paged Responses Are Enumerable
# The simplest way to handle paged response data is to use the built-in
# enumerator in the response object, as shown in the following example.
#
# s3 = Aws::S3::Client.new
#
# s3.list_objects(bucket:'aws-sdk').each do |response|
# puts response.contents.map(&:key)
# end
#
# This yields one response object per API call made, and enumerates objects
# in the named bucket. The SDK retrieves additional pages of data to
# complete the request.
#
# # Handling Paged Responses Manually
# To handle paging yourself, use the response’s `next_page?` method to verify
# there are more pages to retrieve, or use the last_page? method to verify
# there are no more pages to retrieve.
#
# If there are more pages, use the `next_page` method to retrieve the
# next page of results, as shown in the following example.
#
# s3 = Aws::S3::Client.new
#
# # Get the first page of data
# response = s3.list_objects(bucket:'aws-sdk')
#
# # Get additional pages
# while response.next_page? do
# response = response.next_page
# # Use the response data here...
# puts response.contents.map(&:key)
# end
#
module PageableResponse
def self.apply(base)
base.extend Extension
base.instance_variable_set(:@last_page, nil)
base.instance_variable_set(:@more_results, nil)
base
end
# @return [Paging::Pager]
attr_accessor :pager
# Returns `true` if there are no more results. Calling {#next_page}
# when this method returns `false` will raise an error.
# @return [Boolean]
def last_page?
# Actual implementation is in PageableResponse::Extension
end
# Returns `true` if there are more results. Calling {#next_page} will
# return the next response.
# @return [Boolean]
def next_page?
# Actual implementation is in PageableResponse::Extension
end
# @return [Seahorse::Client::Response]
def next_page(params = {})
# Actual implementation is in PageableResponse::Extension
end
# Yields the current and each following response to the given block.
# @yieldparam [Response] response
# @return [Enumerable,nil] Returns a new Enumerable if no block is given.
def each(&block)
# Actual implementation is in PageableResponse::Extension
end
alias each_page each
private
# @param [Hash] params A hash of additional request params to
# merge into the next page request.
# @return [Seahorse::Client::Response] Returns the next page of
# results.
def next_response(params)
# Actual implementation is in PageableResponse::Extension
end
# @param [Hash] params A hash of additional request params to
# merge into the next page request.
# @return [Hash] Returns the hash of request parameters for the
# next page, merging any given params.
def next_page_params(params)
# Actual implementation is in PageableResponse::Extension
end
# Raised when calling {PageableResponse#next_page} on a pager that
# is on the last page of results. You can call {PageableResponse#last_page?}
# or {PageableResponse#next_page?} to know if there are more pages.
class LastPageError < RuntimeError
# @param [Seahorse::Client::Response] response
def initialize(response)
@response = response
super("unable to fetch next page, end of results reached")
end
# @return [Seahorse::Client::Response]
attr_reader :response
end
# A handful of Enumerable methods, such as #count are not safe
# to call on a pageable response, as this would trigger n api calls
# simply to count the number of response pages, when likely what is
# wanted is to access count on the data. Same for #to_h.
# @api private
module UnsafeEnumerableMethods
def count
if data.respond_to?(:count)
data.count
else
raise NoMethodError, "undefined method `count'"
end
end
def respond_to?(method_name, *args)
if method_name == :count
data.respond_to?(:count)
else
super
end
end
def to_h
data.to_h
end
def as_json(_options = {})
data.to_h(data, as_json: true)
end
def to_json(options = {})
as_json.to_json(options)
end
end
# The actual decorator module implementation. It is in a distinct module
# so that it can be used to extend objects without busting Ruby's constant cache.
# object.extend(mod) bust the constant cache only if `mod` contains constants of its own.
# @api private
module Extension
include Enumerable
include UnsafeEnumerableMethods
attr_accessor :pager
def last_page?
if @last_page.nil?
@last_page = !@pager.truncated?(self)
end
@last_page
end
def next_page?
!last_page?
end
def next_page(params = {})
if last_page?
raise LastPageError.new(self)
else
next_response(params)
end
end
def each(&block)
return enum_for(:each_page) unless block_given?
response = self
yield(response)
until response.last_page?
response = response.next_page
yield(response)
end
end
alias each_page each
private
def next_response(params)
params = next_page_params(params)
request = context.client.build_request(context.operation_name, params)
Aws::Plugins::UserAgent.metric('PAGINATOR') do
request.send_request
end
end
def next_page_params(params)
# Remove all previous tokens from original params
# Sometimes a token can be nil and merge would not include it.
tokens = @pager.tokens.values.map(&:to_sym)
params_without_tokens = context[:original_params].reject { |k, _v| tokens.include?(k) }
params_without_tokens.merge!(@pager.next_tokens(self).merge(params))
params_without_tokens
end
end
end
end
|