Skip to content

Conversation

@kai-ion
Copy link
Contributor

@kai-ion kai-ion commented Jan 7, 2026

Issue #, if available:

Description of changes:
Adds a generic PagePaginator utility class for iterating through paginated API responses using C++ range-based for loops.

Check all that applies:

  • Did a review by yourself.
  • Added proper tests to cover this PR. (If tests are not applicable, explain.)
  • Checked if this PR is a breaking (APIs have been changed) change.
  • Checked if this PR will not introduce cross-platform inconsistent behavior.
  • Checked if this PR would require a ReadMe/Wiki update.

Check which platforms you have built SDK on to verify the correctness of this PR.

  • Linux
  • Windows
  • Android
  • MacOS
  • IOS
  • Other Platforms

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

class PageIterator
{
public:
PageIterator() = default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why have a default constructor? deafult initialization doesnt sound valid

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I need it because the default constructor creates the "end" iterator for me to check if both iterator have m_atEnd == true and reached the end

m_currentOutcome = OperationTraits::Invoke(*m_client, m_request);
}

ServiceClient* m_client{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if its a raw pointer who owns it? who deletes it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be the caller that owns it

S3Client client;  // Caller
auto paginator = client.ListObjectsPaginated(request);  // Borrows pointer
for (const auto& outcome : paginator) { ... }  
// Paginator destroyed


bool operator!=(const PageIterator& other) const
{
return !(m_atEnd && other.m_atEnd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why for not equals are you only comparing the boolean atEnd and not the other members? specifically m_currentOutcome currently two iterators completely unrelated to eachother would be equal as long as they were not at the end, which is incorrect i think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this comparison we're only detecting "end of iteration". In practice, customers wont be comparing two active iterator, they only compare against the end() iterator.. So we can just check if an iterator is used up.

If we were to compare iterators from different paginators we would have to add overhead and compare m_client pointers.


bool operator==(const PageIterator& other) const
{
return !(*this != other);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets flip this so that equals actually compares and not equals is the negation of equals

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Youre right, let me flip this

if (!m_currentOutcome.IsSuccess())
{
m_atEnd = true;
return *this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how will the error in this case be shown to the customer? your test only works because the initial request fails in the constructor, what if the second or subsequent requests fail? lets add a test for this and fix this.

Copy link
Contributor Author

@kai-ion kai-ion Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, I added a new test TestHandlesErrorOnSecondPage to test that errors stored in (m_currentOutcome) on subsequent pages (page 2) are properly shown to customers.

customers can check outcome.IsSuccess() for the error

{

template <class ServiceClient, class OperationRequest, class OperationTraits>
class PagePaginator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think we want to make this a iterator_trait yeah? i.e.

The template can be specialized for user-defined iterators so that the information about the iterator can be retrieved even if the type does not provide the usual typedefs.

i.e.

class MyIterator {
public:
    using iterator_category = std::forward_iterator_tag;
    using value_type = int;
    using difference_type = std::ptrdiff_t;
    using pointer = int*;
    using reference = int&;

    // Iterator operations...
};

this is so that std lib algorithms and other iterator based libraries can treat it as a iterator officially

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added new iterator_trait

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants