Add pagination support

I think it would be nice to reduce the user-side boilerplate for pagination when we move to 1.23 and iterator support.

This should mostly target the List*() methods. Most of them support pagination via the embedded ListOptions type. Since it's embedded, we can't really use it directly or implement an interface for it that we could use in a generic "pagination" function. However, we could do something like the following:

package gitlab

import (
	"fmt"
	"iter"
	"slices"
)

func Scan2[T any](f func(page int) ([]T, *Response, error)) iter.Seq2[T, error] {
	return func(yield func(T, error) bool) {
		page := 1

		for page != 0 {
			ts, resp, err := f(page)
			if err != nil {
				var t T
				yield(t, err)
				return
			}

			for _, t := range ts {
				if !yield(t, nil) {
					return
				}
			}

			page = resp.NextPage
		}
	}
}

func Scan[T any](f func(page int) ([]T, *Response, error)) (iter.Seq[T], func() error) {
	var e error = nil
	return func(yield func(T) bool) {
		page := 1

		for page != 0 {
			ts, resp, err := f(page)
			if err != nil {
				e = err
				return
			}

			for _, t := range ts {
				if !yield(t) {
					return
				}
			}

			page = resp.NextPage
		}
	}, func() error {
		return e
	}
}

func Example() {
	c, err := NewClient("xxx")
	if err != nil {
		panic(err)
	}

	projectID := 42
	_, _, err = c.ProjectAccessTokens.ListProjectAccessTokens(projectID, nil)
	if err != nil {
		panic(err)
	}

	opts := &ListProjectAccessTokensOptions{}
	it, hasErr := Scan(func(page int) ([]*ProjectAccessToken, *Response, error) {
		opts.Page = page
		return c.ProjectAccessTokens.ListProjectAccessTokens(projectID, opts)
	})

	tokens := slices.Collect(it)
	if err := hasErr(); err != nil {
		panic(err)
	}
	fmt.Printf("Tokens: %+v\n", tokens)

	opts2 := &ListProjectAccessTokensOptions{}
	it2 := Scan2(func(page int) ([]*ProjectAccessToken, *Response, error) {
		opts2.Page = page
		return c.ProjectAccessTokens.ListProjectAccessTokens(projectID, opts2)
	})

	for token, err := range it2 {
		if err != nil {
			panic(err)
		}

		fmt.Printf("foo: %s\n", token.Name)
	}
}

This implements two pagination functions, Scan and Scan2 where the difference is about error reporting. The former returns an iterator and a function to retrieve potential errors. This needs to be checked after pulling values from the iterator. The latter returns the paginated object and an error that needs to be checked in every pull from the iterator. The Example function shows that.

I'm wondering what other options we have. Let's collect them here.

One that I didn't flesh out it is to introduce a new RequestOptionFunc that overwrites the current page. Yet, we'd still have to pass that to every List*() call and basically something like the page parameter of the above Scan*() funcs would still be required.

Obviously, we could introduce a Pager interface and implement that on every List*Options type that controls the ListOptions.Page field. I'm not sure how big the benefit would be though.

Edited by Timo Furrer
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information