File: pageable_response.rb

package info (click to toggle)
ruby-aws-sdk-core 3.212.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,232 kB
  • sloc: ruby: 17,533; makefile: 4
file content (221 lines) | stat: -rw-r--r-- 6,879 bytes parent folder | download
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