From 0beacfdde4fadedb0f5a12b2b8efa6c0722c56ab Mon Sep 17 00:00:00 2001 From: Alexander Staubo Date: Mon, 4 Feb 2019 13:26:47 -0500 Subject: [PATCH 1/2] Support custom map types in resolvers (e.g. `type Thing map[string]interface{}`) by falling back to reflection. Fixes #439. --- executor.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/executor.go b/executor.go index 10d3799f..333947f7 100644 --- a/executor.go +++ b/executor.go @@ -964,6 +964,22 @@ func DefaultResolveFn(p ResolveParams) (interface{}, error) { return property, nil } + // Try accessing as map via reflection + if r := reflect.ValueOf(p.Source); r.Kind() == reflect.Map && r.Type().Key().Kind() == reflect.String { + val := r.MapIndex(reflect.ValueOf(p.Info.FieldName)) + if val.IsValid() { + property := val.Interface() + if val.Type().Kind() == reflect.Func { + // try type casting the func to the most basic func signature + // for more complex signatures, user have to define ResolveFn + if propertyFn, ok := property.(func() interface{}); ok { + return propertyFn(), nil + } + } + return property, nil + } + } + // last resort, return nil return nil, nil } From db6bc15feab150eae1f2bde7e1556ce42847b041 Mon Sep 17 00:00:00 2001 From: Alexander Staubo Date: Mon, 4 Feb 2019 13:38:45 -0500 Subject: [PATCH 2/2] Adds regression test for #439. --- executor_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/executor_test.go b/executor_test.go index 081b6c07..e72e900a 100644 --- a/executor_test.go +++ b/executor_test.go @@ -297,6 +297,62 @@ func TestMergesParallelFragments(t *testing.T) { } } +type CustomMap map[string]interface{} + +func TestCustomMapType(t *testing.T) { + query := ` + query Example { data { a } } + ` + data := CustomMap{ + "a": "1", + "b": "2", + } + schema, err := graphql.NewSchema(graphql.SchemaConfig{ + Query: graphql.NewObject(graphql.ObjectConfig{ + Name: "RootQuery", + Fields: graphql.Fields{ + "data": &graphql.Field{ + Type: graphql.NewObject(graphql.ObjectConfig{ + Name: "Data", + Fields: graphql.Fields{ + "a": &graphql.Field{ + Type: graphql.String, + }, + "b": &graphql.Field{ + Type: graphql.String, + }, + }, + }), + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return data, nil + }, + }, + }, + }), + }) + if err != nil { + t.Fatalf("Error in schema %v", err.Error()) + } + + result := testutil.TestExecute(t, graphql.ExecuteParams{ + Schema: schema, + Root: data, + AST: testutil.TestParse(t, query), + }) + if len(result.Errors) > 0 { + t.Fatalf("wrong result, unexpected errors: %v", result.Errors) + } + + expected := map[string]interface{}{ + "data": map[string]interface{}{ + "a": "1", + }, + } + if !reflect.DeepEqual(result.Data, expected) { + t.Fatalf("Expected context.key to equal %v, got %v", expected, result.Data) + } +} + func TestThreadsSourceCorrectly(t *testing.T) { query := `