Skip to content

Commit

Permalink
APIS-4201 Add support for reporting list fields functions (#23)
Browse files Browse the repository at this point in the history
* APIS-4201 Add support for reporting list fields functions
  • Loading branch information
zacaudette authored Feb 27, 2020
1 parent 16c54ba commit 0313f10
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 3 deletions.
31 changes: 31 additions & 0 deletions gql/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ var graphqlKinds = map[reflect.Kind]graphql.Type{
reflect.String: graphql.String,
}

type ListFunctions struct {
SortField string
SortOrder string
Filter string
}

// QueryReporter defines the interface used to report details on the GraphQL queries being performed.
// Implementations must be concurrency safe and added to the request context using QueryReporterContextKey as the
// context value key.
Expand All @@ -46,6 +52,15 @@ type QueryReporter interface {
QueriedField(string) error
}

// QueryFunctionReporter defines the interface used to report details on the GraphQL queries functions being performed.
// Query functions include list filtering and list sorting.
// Implementations must be concurrency safe and added to the request context using QueryFunctionReporterContextKey as
// the context value key.
type QueryFunctionReporter interface {
// QueriedListFunctions is called by the list resolve function used for filtered fields within the GraphQL query.
QueriedListFunctions(string, ListFunctions) error
}

// ObjectBuilder is used to build GraphGL Objects based on the fields within a set of structs.
// A graphql.Schema is built with the parameters defined at https://godoc.org/github.com/graphql-go/graphql#SchemaConfig
// this code creates Objects which implement the graphql.Type interface and also graphql.Interface interface.
Expand Down Expand Up @@ -402,6 +417,22 @@ func ResolveListField(name string, parent string) graphql.FieldResolveFn {
return nil, err
}

if qr, ok := p.Context.Value(QueryReporterContextKey).(QueryFunctionReporter); ok && qr != nil {
var lf ListFunctions
if sortParams != nil {
lf.SortField = sortParams.field
lf.SortOrder = sortParams.order
}

if filter != nil {
lf.Filter = filter.json.String()
}

if err := qr.QueriedListFunctions(fmt.Sprintf("%s_%s", parent, name), lf); err != nil {
return nil, err
}
}

resolve := ResolveByField(name, parent)

resolvedValue, err := resolve(p)
Expand Down
16 changes: 16 additions & 0 deletions gql/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"reflect"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -61,11 +62,26 @@ type testDoubleEmbed struct {
}

type testQueryReporter struct {
reporterMux sync.Mutex
queriedField string
sortField string
sortOrder string
filter string
}

func (tqr *testQueryReporter) QueriedField(field string) error {
tqr.reporterMux.Lock()
tqr.queriedField = field
tqr.reporterMux.Unlock()
return nil
}

func (tqr *testQueryReporter) QueriedListFunctions(field string, lf ListFunctions) error {
tqr.reporterMux.Lock()
tqr.sortField = lf.SortField
tqr.sortOrder = lf.SortOrder
tqr.filter = lf.Filter
tqr.reporterMux.Unlock()
return nil
}

Expand Down
12 changes: 11 additions & 1 deletion gql/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"strconv"
"strings"

"github.com/GannettDigital/graphql"
"github.com/GannettDigital/graphql/language/ast"
Expand Down Expand Up @@ -85,6 +86,14 @@ func newListFilterJSON(fields []*ast.ObjectField) (*listFilterJSON, error) {
return &lf, nil
}

func (lf listFilterJSON) String() string {
var arguments []string
for _, arg := range lf.Argument {
arguments = append(arguments, fmt.Sprintf("%v", arg))
}
return fmt.Sprintf("Field:%v, Operation:%v, Arguments:%v", lf.Field, lf.Operation, strings.Join(arguments, ","))
}

// parseASTValue will recursively follow a AST value structure to build up a Golang object.
func parseASTValue(in interface{}) (interface{}, error) {
value, ok := in.(ast.Value)
Expand Down Expand Up @@ -132,6 +141,7 @@ func parseASTValue(in interface{}) (interface{}, error) {
type listFilter struct {
fieldName string
op Comparator
json *listFilterJSON
}

// newListFilter parses a given argument into a listFilter. The type of listFilter returned is based on the operation.
Expand Down Expand Up @@ -162,7 +172,7 @@ func newListFilter(arg interface{}) (*listFilter, error) {
return nil, err
}

return &listFilter{fieldName: lf.Field, op: op}, nil
return &listFilter{fieldName: lf.Field, op: op, json: lf}, nil
}

func (lf listFilter) match(raw interface{}) (bool, error) {
Expand Down
22 changes: 21 additions & 1 deletion gql/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestResolveListField(t *testing.T) {
query string
want string
wantErr bool
wantLF *ListFunctions
}{
{
description: "No filter argument",
Expand Down Expand Up @@ -146,11 +147,25 @@ func TestResolveListField(t *testing.T) {
query: `query { q(id: "1"){ items(filter: {Field: "leaf_falsename", Operation: "==", Argument: {Value: "leaf"}}){name value leaf{ name }}}}`,
want: `{"data":{"q":{"items":[]}}}`,
},
{
description: "reporting 2nd level, string equal filter - second field not found",
query: `query { q(id: "1"){ items(filter: {Field: "leaf_falsename", Operation: "==", Argument: {Value: "leaf"}}){name value leaf{ name }}}}`,
want: `{"data":{"q":{"items":[]}}}`,
wantLF: &ListFunctions{
Filter: "Field:leaf_falsename, Operation:==, Arguments:leaf",
},
},
}

for _, test := range tests {
qr := &testQueryReporter{}
ctx := context.Background()
if test.wantLF != nil {
ctx = context.WithValue(ctx, QueryReporterContextKey, qr)
}

params := graphql.Params{
Context: context.Background(),
Context: ctx,
Schema: s,
RequestString: test.query,
}
Expand All @@ -160,6 +175,11 @@ func TestResolveListField(t *testing.T) {
if len(resp.Errors) != 0 {
err = resp.Errors[0]
}
if test.wantLF != nil {
if got, want := qr.filter, test.wantLF.Filter; got != want {
t.Errorf("Test %q - got filter %q, want %q", test.description, got, want)
}
}
switch {
case test.wantErr && err != nil:
continue
Expand Down
25 changes: 24 additions & 1 deletion gql/sort_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func TestListSort(t *testing.T) {
query string
want string
wantErr bool
wantLF *ListFunctions
}{
{
description: "No sort argument",
Expand Down Expand Up @@ -153,11 +154,25 @@ func TestListSort(t *testing.T) {
query: `query { q(id: "1"){ int64list(sort: {Order: "ASC"},filter: {Operation: "LIMIT", Argument:{Value: 2}})}}`,
want: `{"data":{"q":{"int64list":[11,22]}}}`,
},
{
description: "reporting string sort ascending",
query: `query { q(id: "1"){ items(sort: {Field: "name", Order: "ASC"}){name}}}`,
want: `{"data":{"q":{"items":[{"name":"a"},{"name":"b"},{"name":"c"},{"name":"d"},{"name":"e"}]}}}`,
wantLF: &ListFunctions{
SortField: "name",
SortOrder: "ASC",
},
},
}

for _, test := range tests {
qr := &testQueryReporter{}
ctx := context.Background()
if test.wantLF != nil {
ctx = context.WithValue(ctx, QueryReporterContextKey, qr)
}
params := graphql.Params{
Context: context.Background(),
Context: ctx,
Schema: s,
RequestString: test.query,
}
Expand All @@ -167,6 +182,14 @@ func TestListSort(t *testing.T) {
if len(resp.Errors) != 0 {
err = resp.Errors[0]
}
if test.wantLF != nil {
if got, want := qr.sortField, test.wantLF.SortField; got != want {
t.Errorf("Test %q - got sort field %q, want %q", test.description, got, want)
}
if got, want := qr.sortOrder, test.wantLF.SortOrder; got != want {
t.Errorf("Test %q - got sort order %q, want %q", test.description, got, want)
}
}
switch {
case test.wantErr && err != nil:
continue
Expand Down

0 comments on commit 0313f10

Please sign in to comment.