package graphql import ( "context" "reflect" "github.com/rs/zerolog/log" ) 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) log.Debug().Msgf("using page size %d", pageSize) 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 }