sorare/graphql/paginated_query.go

157 lines
3.7 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{}
}
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{}),
}
}
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
}
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
pageSize := (pq.c.MaxComplexity() - 9) / (GetComplexity(reflect.TypeOf(noop)) + 1)
for {
page, pi, err := pq.getPage(ctx, params, after, pageSize)
if err != nil {
return nil, err
}
for _, item := range page {
res = append(res, item)
if opts.Limit > 0 && len(res) >= opts.Limit {
return res, 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
}
func GetComplexity(t reflect.Type) int {
var checkStruct func(t reflect.Type, complexity *int)
checkStruct = func(t reflect.Type, complexity *int) {
if t.Kind() != reflect.Struct {
return
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct {
*complexity++
tag := field.Tag.Get("graphql")
if len(tag) > 6 && tag[:6] == "... on" {
*complexity--
}
checkStruct(field.Type, complexity)
continue
}
if field.Type.Kind() == reflect.Slice {
*complexity++
tmpcomplexity := 0
checkStruct(field.Type.Elem(), &tmpcomplexity)
*complexity += tmpcomplexity * 10
continue
}
*complexity++
}
}
complexity := 0
checkStruct(t, &complexity)
return complexity
}