Skip to content

Commit

Permalink
use ResolveAllEdges when ResolveTotalCount is absent and no edges are…
Browse files Browse the repository at this point in the history
… selected
  • Loading branch information
ccbrown committed Aug 27, 2024
1 parent 4b8604f commit bce62f1
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 21 deletions.
22 changes: 21 additions & 1 deletion pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ type maxEdgeCountContextKeyType int

var maxEdgeCountContextKey maxEdgeCountContextKeyType

func resolveEdgeSliceLen(edgeSlice any) (any, error) {
edgeSliceValue := reflect.ValueOf(edgeSlice)
if edgeSliceValue.Kind() != reflect.Slice {
return nil, fmt.Errorf("unexpected non-slice type %T for edges", edgeSlice)
}
return edgeSliceValue.Len(), nil
}

// Connection is used to create a connection field that adheres to the GraphQL Cursor Connections
// Specification.
func Connection(config *ConnectionConfig) *graphql.FieldDefinition {
Expand Down Expand Up @@ -524,7 +532,19 @@ func Connection(config *ConnectionConfig) *graphql.FieldDefinition {
// no edges. don't do anything unless pageInfo is requested
return &connection{
ResolveTotalCount: func() (any, error) {
return config.ResolveTotalCount(ctx)
if config.ResolveTotalCount != nil {
return config.ResolveTotalCount(ctx)
} else if config.ResolveAllEdges != nil {
edgeSlice, _, err := config.ResolveAllEdges(ctx)
if err != nil {
return nil, err
}
if edgeSlice, ok := edgeSlice.(graphql.ResolvePromise); ok {
return chain(ctx.Context, edgeSlice, resolveEdgeSliceLen), nil
}
return resolveEdgeSliceLen(edgeSlice)
}
return 0, fmt.Errorf("totalCount is not supported for this connection.")
},
Edges: []edge{},
ResolvePageInfo: func() (any, error) {
Expand Down
93 changes: 73 additions & 20 deletions pagination_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestConnection(t *testing.T) {
EdgeFields: map[string]*graphql.FieldDefinition{
"node": {
Type: graphql.IntType,
Resolve: func(ctx graphql.FieldContext) (interface{}, error) {
Resolve: func(ctx graphql.FieldContext) (any, error) {
return ctx.Object, nil
},
},
Expand All @@ -34,26 +34,26 @@ func TestConnection(t *testing.T) {

config.AddQueryField("connection", Connection(&ConnectionConfig{
NamePrefix: "Test",
ResolveEdges: func(ctx graphql.FieldContext, after, before interface{}, limit int) (edgeSlice interface{}, cursorLess func(a, b interface{}) bool, err error) {
ResolveEdges: func(ctx graphql.FieldContext, after, before any, limit int) (edgeSlice any, cursorLess func(a, b any) bool, err error) {
ret := make([]int, limit)
for i := range ret {
ret[i] = i
}
return ret, func(a, b interface{}) bool {
return ret, func(a, b any) bool {
return false
}, nil
},
ResolveTotalCount: func(ctx graphql.FieldContext) (interface{}, error) {
ResolveTotalCount: func(ctx graphql.FieldContext) (any, error) {
return 1000, nil
},
CursorType: reflect.TypeOf(""),
EdgeCursor: func(edge interface{}) interface{} {
EdgeCursor: func(edge any) any {
return strconv.Itoa(edge.(int))
},
EdgeFields: map[string]*graphql.FieldDefinition{
"node": {
Type: graphql.IntType,
Resolve: func(ctx graphql.FieldContext) (interface{}, error) {
Resolve: func(ctx graphql.FieldContext) (any, error) {
return ctx.Object, nil
},
},
Expand Down Expand Up @@ -175,20 +175,73 @@ func TestConnection_ZeroArg_WithoutPageInfo(t *testing.T) {
config := &Config{}
config.AddQueryField("connection", Connection(&ConnectionConfig{
NamePrefix: "Test",
ResolveEdges: func(ctx graphql.FieldContext, after, before interface{}, limit int) (edgeSlice interface{}, cursorLess func(a, b interface{}) bool, err error) {
ResolveEdges: func(ctx graphql.FieldContext, after, before any, limit int) (edgeSlice any, cursorLess func(a, b any) bool, err error) {
return nil, nil, fmt.Errorf("the edge resolver should not be invoked")
},
ResolveTotalCount: func(ctx graphql.FieldContext) (interface{}, error) {
ResolveTotalCount: func(ctx graphql.FieldContext) (any, error) {
return 1000, nil
},
CursorType: reflect.TypeOf(""),
EdgeCursor: func(edge interface{}) interface{} {
EdgeCursor: func(edge any) any {
return strconv.Itoa(edge.(int))
},
EdgeFields: map[string]*graphql.FieldDefinition{
"node": {
Type: graphql.IntType,
Resolve: func(ctx graphql.FieldContext) (interface{}, error) {
Resolve: func(ctx graphql.FieldContext) (any, error) {
return ctx.Object, nil
},
},
},
}))

api, err := NewAPI(config)
require.NoError(t, err)

req := httptest.NewRequest("POST", "/", strings.NewReader(`{
connection(first: 0) {
edges {
node
}
totalCount
}
}`))
req.Header.Set("Content-Type", "application/graphql")
w := httptest.NewRecorder()

api.ServeGraphQL(w, req)

resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)

assert.JSONEq(t, `{
"data": {
"connection": {
"edges": [],
"totalCount": 1000
}
}
}`, string(body))
}

func TestConnection_ZeroArg_WithoutPageInfo_ResolveAll(t *testing.T) {
config := &Config{}
config.AddQueryField("connection", Connection(&ConnectionConfig{
NamePrefix: "Test",
ResolveAllEdges: func(ctx graphql.FieldContext) (edgeSlice any, cursorLess func(a, b any) bool, err error) {
ret := make([]int, 1000)
return ret, func(a, b any) bool {
return false
}, nil
},
CursorType: reflect.TypeOf(""),
EdgeCursor: func(edge any) any {
return strconv.Itoa(edge.(int))
},
EdgeFields: map[string]*graphql.FieldDefinition{
"node": {
Type: graphql.IntType,
Resolve: func(ctx graphql.FieldContext) (any, error) {
return ctx.Object, nil
},
},
Expand Down Expand Up @@ -228,24 +281,24 @@ func TestConnection_ZeroArg_WithPageInfo(t *testing.T) {
config := &Config{}
config.AddQueryField("connection", Connection(&ConnectionConfig{
NamePrefix: "Test",
ResolveEdges: func(ctx graphql.FieldContext, after, before interface{}, limit int) (edgeSlice interface{}, cursorLess func(a, b interface{}) bool, err error) {
return Go(ctx.Context, func() (interface{}, error) {
ResolveEdges: func(ctx graphql.FieldContext, after, before any, limit int) (edgeSlice any, cursorLess func(a, b any) bool, err error) {
return Go(ctx.Context, func() (any, error) {
return make([]int, limit), nil
}), func(a, b interface{}) bool {
}), func(a, b any) bool {
return false
}, nil
},
ResolveTotalCount: func(ctx graphql.FieldContext) (interface{}, error) {
ResolveTotalCount: func(ctx graphql.FieldContext) (any, error) {
return 1000, nil
},
CursorType: reflect.TypeOf(""),
EdgeCursor: func(edge interface{}) interface{} {
EdgeCursor: func(edge any) any {
return strconv.Itoa(edge.(int))
},
EdgeFields: map[string]*graphql.FieldDefinition{
"node": {
Type: graphql.IntType,
Resolve: func(ctx graphql.FieldContext) (interface{}, error) {
Resolve: func(ctx graphql.FieldContext) (any, error) {
return ctx.Object, nil
},
},
Expand Down Expand Up @@ -306,7 +359,7 @@ func TestTimeBasedConnection(t *testing.T) {
Type: graphql.BooleanType,
},
},
EdgeGetter: func(ctx graphql.FieldContext, minTime time.Time, maxTime time.Time, limit int) (interface{}, error) {
EdgeGetter: func(ctx graphql.FieldContext, minTime time.Time, maxTime time.Time, limit int) (any, error) {
if limit == 0 {
return nil, nil
}
Expand All @@ -317,19 +370,19 @@ func TestTimeBasedConnection(t *testing.T) {
}
}
if async, ok := ctx.Arguments["async"].(bool); ok && async {
return Go(ctx.Context, func() (interface{}, error) {
return Go(ctx.Context, func() (any, error) {
return ret, nil
}), nil
}
return ret, nil
},
EdgeCursor: func(edge interface{}) TimeBasedCursor {
EdgeCursor: func(edge any) TimeBasedCursor {
return NewTimeBasedCursor(edge.(time.Time), "")
},
EdgeFields: map[string]*graphql.FieldDefinition{
"node": {
Type: DateTimeType,
Resolve: func(ctx graphql.FieldContext) (interface{}, error) {
Resolve: func(ctx graphql.FieldContext) (any, error) {
return ctx.Object, nil
},
},
Expand Down

0 comments on commit bce62f1

Please sign in to comment.