1616#define GAPIC_GENERATOR_CPP_GAX_PAGINATION_H_
1717
1818#include " gax/internal/invoke_result.h"
19+ #include " gax/status.h"
1920#include < google/protobuf/repeated_field.h>
20- #include < functional>
2121#include < iterator>
2222#include < string>
2323#include < type_traits>
2424
2525namespace google {
2626namespace gax {
2727
28- template <typename ElementType, typename PageType, typename ElementAccessor>
28+ /* *
29+ * Wraps a 'page' message with a consistent interface that provides an iterator
30+ * over its repeated elements and an accessor for its next_page_token field.
31+ *
32+ * Picking a repeated field, deducing the type of its elements, and providing an
33+ * ElementAccessor functor that references the field is outside the scope of the
34+ * template.
35+ *
36+ * @tparam ElementType the type of the repeated elements in the page.
37+ * @tparam PageType the type of the wrapped message.
38+ * @tparam ElementAccessor a default-constructable functor that has an overload
39+ * of operator() that takes a PageType& and returns a mutable pointer to the
40+ * repeated field that contains ElementType.
41+ *
42+ * Note: if there is more than one repeated field in a PageType message, all but
43+ * one will be efectively hidden except through the RawPage accessor.
44+ */
45+ template <
46+ typename ElementType, typename PageType, typename ElementAccessor,
47+ typename std::enable_if<
48+ gax::internal::is_invocable<ElementAccessor, PageType&>::value,
49+ int >::type = 0 ,
50+ typename std::enable_if<
51+ std::is_default_constructible<ElementAccessor>::value, int >::type = 0 >
2952class PageResult {
3053 using FieldIterator =
3154 typename std::remove_pointer<typename gax::internal::invoke_result_t <
@@ -40,8 +63,8 @@ class PageResult {
4063 using pointer = ElementType*;
4164 using reference = ElementType&;
4265
43- reference operator *() const { return *current_; }
44- pointer operator ->() const { return &(*current_); }
66+ ElementType& operator *() const { return *current_; }
67+ ElementType* operator ->() const { return &(*current_); }
4568 iterator& operator ++() {
4669 ++current_;
4770 return *this ;
@@ -51,25 +74,186 @@ class PageResult {
5174 }
5275 bool operator !=(iterator const & rhs) const { return !(*this == rhs); }
5376
77+ private:
78+ friend PageResult;
5479 iterator (FieldIterator current) : current_(std::move(current)) {}
5580
5681 FieldIterator current_;
5782 };
58- // Note: Since the constructor will eventually be private (with friend
59- // access), we don't have to define all the variants for references.
60- // We just want to take ownership of a page.
61- PageResult (PageType&& raw_page, ElementAccessor accessor)
62- : raw_page_(std::move(raw_page)), accessor_(accessor) {}
6383
64- iterator begin () { return accessor_ ( raw_page_)-> begin (); }
65- iterator end () { return accessor_ ( raw_page_)-> end (); }
84+ PageResult (PageType const & raw_page) : raw_page_(raw_page) { }
85+ PageResult (PageType&& raw_page) : raw_page_(std::move(raw_page)) { }
6686
87+ iterator begin () { return ElementAccessor{}(raw_page_)->begin (); }
88+ iterator end () { return ElementAccessor{}(raw_page_)->end (); }
89+
90+ // Const overloads
91+ iterator begin () const { return ElementAccessor{}(raw_page_)->cbegin (); }
92+ iterator end () const { return ElementAccessor{}(raw_page_)->cend (); }
93+
94+ /* *
95+ * @brief Get the next_page_token for the page.
96+ *
97+ * @retun the token for the next page in the sequence
98+ * or the empty string if this is the last page.
99+ */
67100 std::string NextPageToken () const { return raw_page_.next_page_token (); }
101+
102+ /* *
103+ * @brief Get the underlying page message.
104+ *
105+ * This can be useful if the page has fields other than the repeated field or
106+ * next page token.
107+ *
108+ * @return a const reference to the underlying raw page.
109+ */
68110 PageType const & RawPage () const { return raw_page_; }
69111
112+ // Note: the non-const variant is intended for internal use only.
113+ PageType& RawPage () { return raw_page_; }
114+
70115 private:
71116 PageType raw_page_;
72- ElementAccessor accessor_;
117+ };
118+
119+ /* *
120+ * Wraps a sequence of pages implied to be serially returned by a paginated API
121+ * method and provides an iterator that retrieves subsequent pages, usually via
122+ * synchronous rpc.
123+ *
124+ * Determining whether a protobuf message constitutes a valid PageType is
125+ * outside the scope of the template code.
126+ *
127+ * @par Example
128+ *
129+ * @code
130+ * ListElementsRequest request;
131+ * // Set up request to describe the right resource
132+ * // and optionally customize the initial page token or page size.
133+ *
134+ * class ElementsAccessor {
135+ * public:
136+ * protobuf::RepeatedPtrField<Element>*
137+ * operator()(ListElementsResponse* response) {
138+ * return response.mutable_elements();
139+ * }
140+ * };
141+ *
142+ * auto get_next_page = [request, stub](ListElementsResponse* response) mutable
143+ * {
144+ * gax::CallContext ctx;
145+ * gax::Status status = stub->ListElements(context, request, response);
146+ * request.set_next_page_token(response->next_page_token());
147+ * return status;
148+ * };
149+ *
150+ * // Only list up to 20 pages, even though there may be more.
151+ * Pages<EltType, ListElementsResponse, decltype(get_next_page),
152+ * ElementsAccessor> pages(std::move(get_next_page), 20);
153+ * for(auto& page : pages) {
154+ * // Do something with the page
155+ * }
156+ * @endcode
157+ *
158+ * @tparam ElementType the type of the repeated elements in the page.
159+ * @tparam PageType thye type of the wrapped message.
160+ * @tparam ElementAccessor a default-constructable functor that has an overload
161+ * of operator() that takes a PageType& and returns a mutable pointer to the
162+ * repeated field that contains ElementType.
163+ * @tparam NextPageRetriever a copy-constructable functor that has an overload
164+ * of operator() that takes a mutable PageType*, initiates an rpc that
165+ * overwrites the contents of the page with the next page, and returns a
166+ * gax::Status indicating the success or failure of the rpc.
167+ *
168+ * Note: the initial page request MUST be captured by value in the
169+ * NextPageRetriever functor so that calling begin() multiple times on a Pages
170+ * instance results in valid behavior.
171+ */
172+ template <typename ElementType, typename PageType, typename ElementAccessor,
173+ typename NextPageRetriever,
174+ typename std::enable_if<
175+ gax::internal::is_invocable<NextPageRetriever, PageType*>::value,
176+ int >::type = 0 ,
177+ typename std::enable_if<
178+ std::is_copy_constructible<NextPageRetriever>::value, int >::type =
179+ 0 >
180+ class Pages {
181+ public:
182+ class iterator {
183+ public:
184+ using PageResultT = PageResult<ElementType, PageType, ElementAccessor>;
185+ using iterator_category = std::forward_iterator_tag;
186+ using value_type = PageResultT;
187+ using difference_type = std::ptrdiff_t ;
188+ using pointer = PageResultT*;
189+ using reference = PageResultT&;
190+
191+ PageResultT const & operator *() const { return page_result_; }
192+ PageResultT const * operator ->() const { return &page_result_; }
193+ iterator& operator ++() {
194+ // Note: if the rpc fails, the page will be untouched,
195+ // i.e. will have an empty page token and element collection.
196+ // This invalidates any iterators on the PageResult.
197+ page_result_.RawPage ().Clear ();
198+ get_next_page_ (&(page_result_.RawPage ()));
199+ num_pages_++;
200+ return *this ;
201+ }
202+
203+ // Just want to compare against end()
204+ bool operator ==(iterator const & rhs) const {
205+ return num_pages_ == rhs.num_pages_ ||
206+ page_result_.NextPageToken () == rhs.page_result_ .NextPageToken ();
207+ }
208+ bool operator !=(iterator const & rhs) const { return !(*this == rhs); }
209+
210+ private:
211+ friend Pages;
212+ // Note: copying a message with many repeated elements is expensive.
213+ // Callers should move pages in when instantiating an iterator.
214+ iterator (PageType page_result, NextPageRetriever get_next_page,
215+ int num_pages)
216+ : page_result_(std::move(page_result)),
217+ get_next_page_ (std::move(get_next_page)),
218+ num_pages_(num_pages) {}
219+
220+ PageResultT page_result_;
221+ NextPageRetriever get_next_page_;
222+ int num_pages_;
223+ };
224+
225+ /* *
226+ * Create a new Pages instance using the provided page retrieval functor.
227+ * Iterators on the instance will yield a number of pages up to the page cap.
228+ *
229+ * @param get_next_page an instance of the page retrieval functor.
230+ * @param pages_cap the maximum number of pages to retrieve. A value of 0
231+ * (default) indicates no cap.
232+ */
233+ Pages (NextPageRetriever get_next_page, int pages_cap = 0 )
234+ : get_next_page_(std::move(get_next_page)), pages_cap_(pages_cap) {}
235+
236+ iterator begin () const {
237+ PageType page;
238+ // Copying the next-page lambda is necessary to start at the beginning.
239+ NextPageRetriever fresh_get_next_page_ (get_next_page_);
240+ fresh_get_next_page_ (&page);
241+
242+ return iterator (std::move (page), std::move (fresh_get_next_page_), 1 );
243+ }
244+
245+ iterator end () const {
246+ return iterator{PageType{}, get_next_page_, pages_cap_};
247+ }
248+
249+ private:
250+ // Note: be sure to capture the initial page request by value so that calling
251+ // begin() multiple times is valid.
252+ // This means that whenever get_next_page_ is copied, i.e. whenever the user
253+ // calls Pages::begin(), a fresh copy of the initial request gets created,
254+ // which means that begin() _really_ starts at the beginning.
255+ NextPageRetriever get_next_page_;
256+ const int pages_cap_;
73257};
74258
75259} // namespace gax
0 commit comments