-
Notifications
You must be signed in to change notification settings - Fork 58
/
array_connection.go
187 lines (159 loc) · 4.07 KB
/
array_connection.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package relay
import (
"encoding/base64"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
)
const PREFIX = "arrayconnection:"
type ArraySliceMetaInfo struct {
SliceStart int `json:"sliceStart"`
ArrayLength int `json:"arrayLength"`
}
/*
A simple function that accepts an array and connection arguments, and returns
a connection object for use in GraphQL. It uses array offsets as pagination,
so pagination will only work if the array is static.
*/
func ConnectionFromArray(data []interface{}, args ConnectionArguments) *Connection {
return ConnectionFromArraySlice(
data,
args,
ArraySliceMetaInfo{
SliceStart: 0,
ArrayLength: len(data),
},
)
}
/*
Given a slice (subset) of an array, returns a connection object for use in
GraphQL.
This function is similar to `ConnectionFromArray`, but is intended for use
cases where you know the cardinality of the connection, consider it too large
to materialize the entire array, and instead wish pass in a slice of the
total result large enough to cover the range specified in `args`.
*/
func ConnectionFromArraySlice(
arraySlice []interface{},
args ConnectionArguments,
meta ArraySliceMetaInfo,
) *Connection {
sliceEnd := meta.SliceStart + len(arraySlice)
beforeOffset := GetOffsetWithDefault(args.Before, meta.ArrayLength)
afterOffset := GetOffsetWithDefault(args.After, -1)
startOffset := ternaryMax(meta.SliceStart-1, afterOffset, -1) + 1
endOffset := ternaryMin(sliceEnd, beforeOffset, meta.ArrayLength)
if args.First != -1 {
endOffset = min(endOffset, startOffset+args.First)
}
if args.Last != -1 {
startOffset = max(startOffset, endOffset-args.Last)
}
begin := max(startOffset-meta.SliceStart, 0)
end := len(arraySlice) - (sliceEnd - endOffset)
if begin > end {
return NewConnection()
}
slice := arraySlice[begin:end]
edges := []*Edge{}
for index, value := range slice {
edges = append(edges, &Edge{
Cursor: OffsetToCursor(startOffset + index),
Node: value,
})
}
var firstEdgeCursor, lastEdgeCursor ConnectionCursor
if len(edges) > 0 {
firstEdgeCursor = edges[0].Cursor
lastEdgeCursor = edges[len(edges)-1:][0].Cursor
}
lowerBound := 0
if len(args.After) > 0 {
lowerBound = afterOffset + 1
}
upperBound := meta.ArrayLength
if len(args.Before) > 0 {
upperBound = beforeOffset
}
hasPreviousPage := false
if args.Last != -1 {
hasPreviousPage = startOffset > lowerBound
}
hasNextPage := false
if args.First != -1 {
hasNextPage = endOffset < upperBound
}
conn := NewConnection()
conn.Edges = edges
conn.PageInfo = PageInfo{
StartCursor: firstEdgeCursor,
EndCursor: lastEdgeCursor,
HasPreviousPage: hasPreviousPage,
HasNextPage: hasNextPage,
}
return conn
}
// Creates the cursor string from an offset
func OffsetToCursor(offset int) ConnectionCursor {
str := fmt.Sprintf("%v%v", PREFIX, offset)
return ConnectionCursor(base64.StdEncoding.EncodeToString([]byte(str)))
}
// Re-derives the offset from the cursor string.
func CursorToOffset(cursor ConnectionCursor) (int, error) {
str := ""
b, err := base64.StdEncoding.DecodeString(string(cursor))
if err == nil {
str = string(b)
}
str = strings.Replace(str, PREFIX, "", -1)
offset, err := strconv.Atoi(str)
if err != nil {
return 0, errors.New("Invalid cursor")
}
return offset, nil
}
// Return the cursor associated with an object in an array.
func CursorForObjectInConnection(data []interface{}, object interface{}) ConnectionCursor {
offset := -1
for i, d := range data {
// TODO: better object comparison
if reflect.DeepEqual(d, object) {
offset = i
break
}
}
if offset == -1 {
return ""
}
return OffsetToCursor(offset)
}
func GetOffsetWithDefault(cursor ConnectionCursor, defaultOffset int) int {
if cursor == "" {
return defaultOffset
}
offset, err := CursorToOffset(cursor)
if err != nil {
return defaultOffset
}
return offset
}
func max(a, b int) int {
if a < b {
return b
}
return a
}
func ternaryMax(a, b, c int) int {
return max(max(a, b), c)
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
func ternaryMin(a, b, c int) int {
return min(min(a, b), c)
}