| |
|
|
| package openmeter |
|
|
| import ( |
| "fmt" |
| "iter" |
| "net/url" |
| "strconv" |
| ) |
|
|
| const defaultListPageSize = 100 |
| const maxPages = 10_000 |
|
|
| |
| type PageParams struct { |
| Size *int |
| Number *int |
| } |
|
|
| |
| type CursorPageParams struct { |
| Size *int |
| After *string |
| Before *string |
| } |
|
|
| type PageMeta struct { |
| Number int `json:"number"` |
| Size int `json:"size"` |
| Total int `json:"total"` |
| } |
|
|
| type PaginatedMeta struct { |
| Page PageMeta `json:"page"` |
| } |
|
|
| func addPageParams(q url.Values, page *PageParams) { |
| if page == nil { |
| return |
| } |
| if page.Size != nil { |
| setDeepObjectString(q, "page", "size", strconv.Itoa(*page.Size)) |
| } |
| if page.Number != nil { |
| setDeepObjectString(q, "page", "number", strconv.Itoa(*page.Number)) |
| } |
| } |
|
|
| func addCursorPageParams(q url.Values, page *CursorPageParams) { |
| if page == nil { |
| return |
| } |
| if page.Size != nil { |
| setDeepObjectString(q, "page", "size", strconv.Itoa(*page.Size)) |
| } |
| if page.After != nil { |
| setDeepObjectString(q, "page", "after", *page.After) |
| } |
| if page.Before != nil { |
| setDeepObjectString(q, "page", "before", *page.Before) |
| } |
| } |
|
|
| func paginate[T any](start *PageParams, fetch func(page, size int) ([]T, int, error)) iter.Seq2[T, error] { |
| return func(yield func(T, error) bool) { |
| page, size := 1, defaultListPageSize |
| if start != nil { |
| if start.Number != nil { |
| page = *start.Number |
| } |
| if start.Size != nil { |
| size = *start.Size |
| } |
| } |
| seen := 0 |
| for fetched := 0; fetched < maxPages; fetched++ { |
| data, total, err := fetch(page, size) |
| if err != nil { |
| var zero T |
| yield(zero, err) |
| return |
| } |
| for _, item := range data { |
| if !yield(item, nil) { |
| return |
| } |
| } |
| seen += len(data) |
| if len(data) == 0 || (total > 0 && seen >= total) { |
| return |
| } |
| page++ |
| } |
| var zero T |
| yield(zero, fmt.Errorf("openmeter: pagination did not terminate within %d pages", maxPages)) |
| } |
| } |
|
|
| func paginateCursor[T any](start *CursorPageParams, fetch func(after, before *string, size int) ([]T, *string, *string, error)) iter.Seq2[T, error] { |
| return func(yield func(T, error) bool) { |
| size := defaultListPageSize |
| var after, before *string |
| if start != nil { |
| after = start.After |
| before = start.Before |
| if after != nil && before != nil { |
| var zero T |
| yield(zero, fmt.Errorf("openmeter: cursor pagination cannot use both after and before")) |
| return |
| } |
| if start.Size != nil { |
| size = *start.Size |
| } |
| } |
| reverse := before != nil |
| for fetched := 0; fetched < maxPages; fetched++ { |
| data, next, previous, err := fetch(after, before, size) |
| if err != nil { |
| var zero T |
| yield(zero, err) |
| return |
| } |
| for _, item := range data { |
| if !yield(item, nil) { |
| return |
| } |
| } |
| if reverse { |
| if previous == nil || *previous == "" || len(data) == 0 { |
| return |
| } |
| before = previous |
| } else { |
| if next == nil || *next == "" || len(data) == 0 { |
| return |
| } |
| after = next |
| } |
| } |
| var zero T |
| yield(zero, fmt.Errorf("openmeter: cursor pagination did not terminate within %d pages", maxPages)) |
| } |
| } |
|
|