sorare/graphql/paginated_query.go

142 lines
3.4 KiB
Go

package graphql
import (
"context"
"reflect"
)
type PaginatedQuery[ResultType any, Params any] struct {
c *Client
gqltype string
containerLayers []string
additionalPayloadParams map[string]interface{}
additionalQueryParams map[string]interface{}
overrideComplexity int
}
func NewPaginatedQuery[ResultType any, Params any](
c *Client,
gqlType string,
containerLayers []string,
) *PaginatedQuery[ResultType, Params] {
return &PaginatedQuery[ResultType, Params]{
c: c,
gqltype: gqlType,
containerLayers: containerLayers,
additionalPayloadParams: make(map[string]interface{}),
additionalQueryParams: make(map[string]interface{}),
overrideComplexity: 0,
}
}
func (pq *PaginatedQuery[ResultType, Params]) WithPayloadParam(
name string,
value interface{},
) *PaginatedQuery[ResultType, Params] {
pq.additionalPayloadParams[name] = value
return pq
}
func (pq *PaginatedQuery[ResultType, Params]) WithQueryParam(
name string,
value interface{},
) *PaginatedQuery[ResultType, Params] {
pq.additionalQueryParams[name] = value
return pq
}
func (pq *PaginatedQuery[ResultType, Params]) WithOverrideComplexity(complexity int) *PaginatedQuery[ResultType, Params] {
pq.overrideComplexity = complexity
return pq
}
type PaginatedQueryGetOptions struct {
Limit int
}
func WithPaginatedQueryLimit(limit int) func(options *PaginatedQueryGetOptions) {
return func(options *PaginatedQueryGetOptions) {
options.Limit = limit
}
}
func (pq *PaginatedQuery[ResultType, Params]) Get(
ctx context.Context,
params Params,
options ...func(options *PaginatedQueryGetOptions),
) ([]ResultType, error) {
opts := &PaginatedQueryGetOptions{
Limit: 0,
}
for _, opt := range options {
opt(opts)
}
var res []ResultType
after := ""
var noop ResultType
complexity := pq.overrideComplexity
if complexity == 0 {
complexity = GetTypeComplexity(reflect.TypeOf(noop))
}
maxPageSize := (pq.c.MaxQueryComplexity() - 7 - len(pq.containerLayers)) / complexity
for {
pageSize := maxPageSize
if opts.Limit > 0 {
remaining := opts.Limit - len(res)
if remaining < pageSize {
pageSize = remaining
}
}
page, pi, err := pq.getPage(ctx, params, after, pageSize)
if err != nil {
return nil, err
}
res = append(res, page...)
if opts.Limit > 0 && len(res) >= opts.Limit {
return res[:opts.Limit], nil
}
if !pi.HasNextPage {
break
}
after = pi.EndCursor
}
return res, nil
}
type PageInfo struct {
EndCursor string `graphql:"endCursor"`
HasNextPage bool `graphql:"hasNextPage"`
HasPreviousPage bool `graphql:"hasPreviousPage"`
StartCursor string `graphql:"startCursor"`
}
func (pq *PaginatedQuery[ResultType, Params]) getPage(
ctx context.Context,
params Params,
after string,
first int,
) ([]ResultType, PageInfo, error) {
type PageResult struct {
Nodes []ResultType `graphql:"nodes"`
PageInfo PageInfo `graphql:"pageInfo"`
}
q := NewQuery[PageResult, Params](pq.c, pq.gqltype, pq.containerLayers).
WithPayloadParam("first", first)
if after != "" {
q = q.WithPayloadParam("after", after)
}
for k, v := range pq.additionalPayloadParams {
q.WithPayloadParam(k, v)
}
for k, v := range pq.additionalQueryParams {
q.WithQueryParam(k, v)
}
res, err := q.Get(ctx, params)
if err != nil {
return nil, PageInfo{}, err
}
return res.Nodes, res.PageInfo, nil
}