177 lines
4.1 KiB
Go
177 lines
4.1 KiB
Go
// Package pagination provides pagination support for API responses.
|
|
package pagination
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// PaginatedResponse represents a paginated response from the API.
|
|
type PaginatedResponse[T any] struct {
|
|
// Results contains the items in this page
|
|
Results []T
|
|
|
|
// Count is the total number of items (if available)
|
|
Count int
|
|
|
|
// Limit is the maximum number of items per page
|
|
Limit int
|
|
|
|
// Offset is the offset of the first item in this page
|
|
Offset int
|
|
|
|
// HasMore indicates if there are more items to fetch
|
|
HasMore bool
|
|
}
|
|
|
|
// FetchFunc is a function that fetches a page of results.
|
|
type FetchFunc[T any] func(ctx context.Context, offset, limit int) (*PaginatedResponse[T], error)
|
|
|
|
// Iterator provides iteration over paginated results.
|
|
type Iterator[T any] struct {
|
|
fetchFunc FetchFunc[T]
|
|
limit int
|
|
offset int
|
|
current *PaginatedResponse[T]
|
|
index int
|
|
err error
|
|
done bool
|
|
}
|
|
|
|
// NewIterator creates a new pagination iterator.
|
|
func NewIterator[T any](fetchFunc FetchFunc[T], limit int) *Iterator[T] {
|
|
if limit <= 0 {
|
|
limit = 100 // Default page size
|
|
}
|
|
return &Iterator[T]{
|
|
fetchFunc: fetchFunc,
|
|
limit: limit,
|
|
offset: 0,
|
|
index: -1,
|
|
}
|
|
}
|
|
|
|
// Next advances the iterator to the next item.
|
|
// It returns true if there is an item available, false otherwise.
|
|
func (i *Iterator[T]) Next(ctx context.Context) bool {
|
|
if i.done || i.err != nil {
|
|
return false
|
|
}
|
|
|
|
// If we don't have a current page or we've reached the end of it, fetch the next page
|
|
if i.current == nil || i.index >= len(i.current.Results)-1 {
|
|
// Check if we know there are no more pages
|
|
if i.current != nil && !i.current.HasMore {
|
|
i.done = true
|
|
return false
|
|
}
|
|
|
|
// Fetch the next page
|
|
page, err := i.fetchFunc(ctx, i.offset, i.limit)
|
|
if err != nil {
|
|
i.err = err
|
|
return false
|
|
}
|
|
|
|
// Check if the page is empty
|
|
if page == nil || len(page.Results) == 0 {
|
|
i.done = true
|
|
return false
|
|
}
|
|
|
|
i.current = page
|
|
i.index = -1
|
|
i.offset += i.limit
|
|
}
|
|
|
|
// Move to the next item in the current page
|
|
i.index++
|
|
return i.index < len(i.current.Results)
|
|
}
|
|
|
|
// Value returns the current item.
|
|
// It should only be called after Next returns true.
|
|
func (i *Iterator[T]) Value() *T {
|
|
if i.current == nil || i.index < 0 || i.index >= len(i.current.Results) {
|
|
return nil
|
|
}
|
|
return &i.current.Results[i.index]
|
|
}
|
|
|
|
// Err returns any error that occurred during iteration.
|
|
func (i *Iterator[T]) Err() error {
|
|
return i.err
|
|
}
|
|
|
|
// Reset resets the iterator to the beginning.
|
|
func (i *Iterator[T]) Reset() {
|
|
i.offset = 0
|
|
i.index = -1
|
|
i.current = nil
|
|
i.err = nil
|
|
i.done = false
|
|
}
|
|
|
|
// Collect fetches all items from all pages and returns them as a slice.
|
|
// Warning: This can be memory intensive for large result sets.
|
|
func (i *Iterator[T]) Collect(ctx context.Context) ([]T, error) {
|
|
var results []T
|
|
for i.Next(ctx) {
|
|
if item := i.Value(); item != nil {
|
|
results = append(results, *item)
|
|
}
|
|
}
|
|
if err := i.Err(); err != nil {
|
|
return nil, fmt.Errorf("failed to collect all items: %w", err)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// CollectWithLimit fetches items up to a maximum count.
|
|
func (i *Iterator[T]) CollectWithLimit(ctx context.Context, maxItems int) ([]T, error) {
|
|
var results []T
|
|
count := 0
|
|
for i.Next(ctx) && count < maxItems {
|
|
if item := i.Value(); item != nil {
|
|
results = append(results, *item)
|
|
count++
|
|
}
|
|
}
|
|
if err := i.Err(); err != nil {
|
|
return nil, fmt.Errorf("failed to collect items: %w", err)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// Page represents a single page of results with metadata.
|
|
type Page[T any] struct {
|
|
Items []T
|
|
Offset int
|
|
Limit int
|
|
Total int
|
|
}
|
|
|
|
// HasNextPage returns true if there are more pages after this one.
|
|
func (p *Page[T]) HasNextPage() bool {
|
|
return p.Offset+len(p.Items) < p.Total
|
|
}
|
|
|
|
// HasPrevPage returns true if there are pages before this one.
|
|
func (p *Page[T]) HasPrevPage() bool {
|
|
return p.Offset > 0
|
|
}
|
|
|
|
// NextOffset returns the offset for the next page.
|
|
func (p *Page[T]) NextOffset() int {
|
|
return p.Offset + p.Limit
|
|
}
|
|
|
|
// PrevOffset returns the offset for the previous page.
|
|
func (p *Page[T]) PrevOffset() int {
|
|
offset := p.Offset - p.Limit
|
|
if offset < 0 {
|
|
return 0
|
|
}
|
|
return offset
|
|
}
|