1package connections
  2
  3import (
  4	"fmt"
  5
  6	"github.com/MichaelMure/git-bug/api/graphql/models"
  7)
  8
  9type Input struct {
 10	After  *string
 11	Before *string
 12	First  *int
 13	Last   *int
 14}
 15
 16// Result is the result of a GraphQL connection pagination
 17type Result[NodeT any] struct {
 18	// A list of edges.
 19	Edges []*Edge[NodeT] `json:"edges"`
 20	Nodes []NodeT        `json:"nodes"`
 21	// Information to aid in pagination.
 22	PageInfo *models.PageInfo `json:"pageInfo"`
 23	// Identifies the total count of items in the connection.
 24	TotalCount int `json:"totalCount"`
 25}
 26
 27// Edge hold a GraphQL connection edge
 28type Edge[NodeT any] struct {
 29	Cursor string `json:"cursor"`
 30	Node   NodeT  `json:"node"`
 31}
 32
 33func newEdge[NodeT any](node NodeT, offset int) *Edge[NodeT] {
 34	return &Edge[NodeT]{Cursor: OffsetToCursor(offset), Node: node}
 35}
 36
 37// Paginate will paginate a source according to the input of a relay connection
 38func Paginate[NodeT any](source []NodeT, input Input) (*Result[NodeT], error) {
 39	var nodes []NodeT
 40	var edges []*Edge[NodeT]
 41	var cursors []string
 42	var pageInfo = &models.PageInfo{}
 43	var totalCount = len(source)
 44
 45	offset := 0
 46
 47	if input.After != nil {
 48		for i, value := range source {
 49			edge := newEdge(value, offset)
 50			if edge.Cursor == *input.After {
 51				// remove all previous element including the "after" one
 52				source = source[i+1:]
 53				offset = i + 1
 54				pageInfo.HasPreviousPage = true
 55				break
 56			}
 57		}
 58	}
 59
 60	if input.Before != nil {
 61		for i, value := range source {
 62			edge := newEdge(value, i+offset)
 63			if edge.Cursor == *input.Before {
 64				// remove all after element including the "before" one
 65				pageInfo.HasNextPage = true
 66				break
 67			}
 68			edges = append(edges, edge)
 69			cursors = append(cursors, edge.Cursor)
 70			nodes = append(nodes, value)
 71		}
 72	} else {
 73		edges = make([]*Edge[NodeT], len(source))
 74		cursors = make([]string, len(source))
 75		nodes = source
 76
 77		for i, value := range source {
 78			edge := newEdge(value, i+offset)
 79			edges[i] = edge
 80			cursors[i] = edge.Cursor
 81		}
 82	}
 83
 84	if input.First != nil {
 85		if *input.First < 0 {
 86			return nil, fmt.Errorf("first less than zero")
 87		}
 88
 89		if len(edges) > *input.First {
 90			// Slice result to be of length first by removing edges from the end
 91			edges = edges[:*input.First]
 92			cursors = cursors[:*input.First]
 93			nodes = nodes[:*input.First]
 94			pageInfo.HasNextPage = true
 95		}
 96	}
 97
 98	if input.Last != nil {
 99		if *input.Last < 0 {
100			return nil, fmt.Errorf("last less than zero")
101		}
102
103		if len(edges) > *input.Last {
104			// Slice result to be of length last by removing edges from the start
105			edges = edges[len(edges)-*input.Last:]
106			cursors = cursors[len(cursors)-*input.Last:]
107			nodes = nodes[len(nodes)-*input.Last:]
108			pageInfo.HasPreviousPage = true
109		}
110	}
111
112	// Fill up pageInfo cursors
113	if len(cursors) > 0 {
114		pageInfo.StartCursor = cursors[0]
115		pageInfo.EndCursor = cursors[len(cursors)-1]
116	}
117
118	return &Result[NodeT]{
119		Edges:      edges,
120		Nodes:      nodes,
121		PageInfo:   pageInfo,
122		TotalCount: totalCount,
123	}, nil
124}