Skip to content

Commit 7527d10

Browse files
authored
Merge pull request #4 from mjarkk/support-graphql-interfaces
Support graphql interfaces
2 parents 1e4fd80 + e77cdda commit 7527d10

14 files changed

Lines changed: 697 additions & 218 deletions

README.md

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (QueryRoot) ResolvePosts() []Post {
4545
type MethodRoot struct{}
4646

4747
func main() {
48-
s := NewSchema()
48+
s := graphql.NewSchema()
4949

5050
err := s.Parse(QueryRoot{}, MethodRoot{}, nil)
5151
if err != nil {
@@ -217,9 +217,8 @@ const (
217217
Grapefruit
218218
)
219219

220-
221220
func main() {
222-
s := NewSchema()
221+
s := graphql.NewSchema()
223222

224223
// The map key is the enum it's key in graphql
225224
// The map value is the go value the enum key is mapped to or the other way around
@@ -234,6 +233,41 @@ func main() {
234233
}
235234
```
236235

236+
### Interfaces
237+
238+
Graphql interfaces can be created using go interfaces
239+
240+
This library needs to anylize all types before you can make a query and as we cannot query all types that implmenet a interface you'll need to help the library with this by calling `Implements` for every implementation.
241+
If `Implements` is not called for a type the response value for that type when inside a interface will always be `null`
242+
243+
```go
244+
type QuerySchema struct {
245+
Bar BarWImpl
246+
Baz BazWImpl
247+
BarOrBaz InterfaceType
248+
}
249+
250+
type InterfaceType interface {
251+
// Interface fields
252+
ResolveFoo() string
253+
ResolveBar() string
254+
}
255+
256+
type BarWImpl struct{}
257+
258+
// Implements hints this library to register BarWImpl
259+
// THIS MUST BE CALLED FOR EVERY TYPE THAT IMPLMENTS InterfaceType
260+
var _ = graphql.Implements((*InterfaceType)(nil), BarWImpl{})
261+
262+
func (BarWImpl) ResolveFoo() string { return "this is bar" }
263+
func (BarWImpl) ResolveBar() string { return "This is bar" }
264+
265+
type BazWImpl struct{}
266+
var _ = graphql.Implements((*InterfaceType)(nil), BazWImpl{})
267+
func (BazWImpl) ResolveFoo() string { return "this is baz" }
268+
func (BazWImpl) ResolveBar() string { return "This is baz" }
269+
```
270+
237271
### Directives
238272

239273
These directives are added by default:
@@ -245,7 +279,7 @@ To add custom directives:
245279

246280
```go
247281
func main() {
248-
s := NewSchema()
282+
s := graphql.NewSchema()
249283

250284
// Also the .RegisterEnum(..) method must be called before .Parse(..)
251285
s.RegisterDirective(Directive{

bytecode/bytecode_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"sync"
99
"testing"
1010

11-
. "github.com/stretchr/testify/assert"
11+
a "github.com/stretchr/testify/assert"
1212
)
1313

1414
func parseQuery(query string) ([]byte, []error) {
@@ -26,9 +26,9 @@ func parseQuery(query string) ([]byte, []error) {
2626
func parseQueryAndExpectErr(t *testing.T, query, expectedErr string) {
2727
_, errs := parseQuery(query)
2828
if len(errs) == 0 {
29-
Fail(t, "exected query to fail with error: "+expectedErr, query)
29+
a.Fail(t, "exected query to fail with error: "+expectedErr, query)
3030
}
31-
Equal(t, errs[0].Error(), expectedErr)
31+
a.Equal(t, errs[0].Error(), expectedErr)
3232
}
3333

3434
func newParseQueryAndExpectResult(t *testing.T, query string, expectedResult []byte, debug ...bool) {
@@ -48,7 +48,7 @@ func newParseQueryAndExpectResult(t *testing.T, query string, expectedResult []b
4848
fmt.Println(expectedResultHex)
4949
}
5050

51-
Equal(t, expectedResultHex, resHex, query)
51+
a.Equal(t, expectedResultHex, resHex, query)
5252
}
5353

5454
func TestParseSimpleQuery(t *testing.T) {

enums_test.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,59 @@ package graphql
33
import (
44
"testing"
55

6-
. "github.com/stretchr/testify/assert"
6+
a "github.com/stretchr/testify/assert"
77
)
88

99
func TestRegisterEnum(t *testing.T) {
1010
type TestEnumString string
1111
res, err := registerEnumCheck(map[string]TestEnumString{
1212
"A": "B",
1313
})
14-
NoError(t, err)
15-
NotNil(t, res)
14+
a.NoError(t, err)
15+
a.NotNil(t, res)
1616

1717
type TestEnumUint uint
1818
res, err = registerEnumCheck(map[string]TestEnumUint{
1919
"A": 1,
2020
})
21-
NoError(t, err)
22-
NotNil(t, res)
21+
a.NoError(t, err)
22+
a.NotNil(t, res)
2323

2424
type TestEnumInt uint
2525
res, err = registerEnumCheck(map[string]TestEnumInt{
2626
"A": 1,
2727
})
28-
NoError(t, err)
29-
NotNil(t, res)
28+
a.NoError(t, err)
29+
a.NotNil(t, res)
3030
}
3131

3232
func TestEmptyEnumShouldNotBeRegistered(t *testing.T) {
3333
type TestEnum string
3434
res, err := registerEnumCheck(map[string]TestEnum{})
35-
NoError(t, err)
36-
Nil(t, res)
35+
a.NoError(t, err)
36+
a.Nil(t, res)
3737
}
3838

3939
func TestRegisterEnumFails(t *testing.T) {
4040
type TestEnum string
4141

4242
_, err := registerEnumCheck(0)
43-
Error(t, err, "Cannot generate an enum of non map types")
43+
a.Error(t, err, "Cannot generate an enum of non map types")
4444

4545
_, err = registerEnumCheck(nil)
46-
Error(t, err, "Cannot generate an enum of non map types 2")
46+
a.Error(t, err, "Cannot generate an enum of non map types 2")
4747

4848
_, err = registerEnumCheck(map[int]TestEnum{1: "a"})
49-
Error(t, err, "Enum must have a string key type")
49+
a.Error(t, err, "Enum must have a string key type")
5050

5151
_, err = registerEnumCheck(map[string]struct{}{"a": {}})
52-
Error(t, err, "Enum value cannot be complex")
52+
a.Error(t, err, "Enum value cannot be complex")
5353

5454
_, err = registerEnumCheck(map[string]string{"foo": "bar"})
55-
Error(t, err, "Enum value must be a custom type")
55+
a.Error(t, err, "Enum value must be a custom type")
5656

5757
_, err = registerEnumCheck(map[string]TestEnum{"": ""})
58-
Error(t, err, "Enum keys cannot be empty")
58+
a.Error(t, err, "Enum keys cannot be empty")
5959

6060
// Maybe fix this??
6161
// _, err = registerEnumCheck(map[string]TestEnum{
@@ -65,13 +65,13 @@ func TestRegisterEnumFails(t *testing.T) {
6565
// Error(t, err, "Enum cannot have duplicated values")
6666

6767
_, err = registerEnumCheck(map[string]TestEnum{"1": ""})
68-
Error(t, err, "Enum cannot have an invalid graphql name, where first letter is number")
68+
a.Error(t, err, "Enum cannot have an invalid graphql name, where first letter is number")
6969

7070
_, err = registerEnumCheck(map[string]TestEnum{"_": ""})
71-
Error(t, err, "Enum cannot have an invalid graphql name, where first letter is underscore")
71+
a.Error(t, err, "Enum cannot have an invalid graphql name, where first letter is underscore")
7272

7373
_, err = registerEnumCheck(map[string]TestEnum{"A!!!!": ""})
74-
Error(t, err, "Enum cannot have an invalid graphql name, where remainder of name is invalid")
74+
a.Error(t, err, "Enum cannot have an invalid graphql name, where remainder of name is invalid")
7575
}
7676

7777
type TestEnum2 uint8
@@ -96,12 +96,12 @@ func TestEnum(t *testing.T) {
9696
"BAR": TestEnum2Bar,
9797
"BAZ": TestEnum2Baz,
9898
})
99-
True(t, added)
100-
NoError(t, err)
99+
a.True(t, added)
100+
a.NoError(t, err)
101101

102102
res, errs := bytecodeParse(t, s, `{bar(e: BAZ)}`, TestEnumFunctionInput{}, M{}, ResolveOptions{NoMeta: true})
103103
for _, err := range errs {
104104
panic(err)
105105
}
106-
Equal(t, `{"bar":"BAZ"}`, res)
106+
a.Equal(t, `{"bar":"BAZ"}`, res)
107107
}

grahql_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ type qlType struct {
8686
Interfaces []qlType `json:"interfaces"`
8787

8888
// INTERFACE and UNION only
89-
PossibleTypes []qlType `json:"possibleTypes"`
89+
PossibleTypes func() []qlType `json:"possibleTypes"`
9090

9191
// ENUM only
9292
EnumValues func(isDeprecatedArgs) []qlEnumValue `json:"-"`

implement_helpers_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import (
55
"strings"
66
"testing"
77

8-
. "github.com/stretchr/testify/assert"
8+
a "github.com/stretchr/testify/assert"
99
)
1010

1111
func TestHandleRequestRequestInURL(t *testing.T) {
1212
s := NewSchema()
1313
err := s.Parse(TestResolveSchemaRequestWithFieldsData{A: TestResolveSchemaRequestWithFieldsDataInnerStruct{Bar: "baz"}}, M{}, nil)
14-
NoError(t, err)
14+
a.NoError(t, err)
1515

1616
res, errs := s.HandleRequest(
1717
"GET",
@@ -31,13 +31,13 @@ func TestHandleRequestRequestInURL(t *testing.T) {
3131
for _, err := range errs {
3232
panic(err)
3333
}
34-
Equal(t, `{"data":{"a":{"bar":"baz"}},"errors":[],"extensions":{}}`, string(res))
34+
a.Equal(t, `{"data":{"a":{"bar":"baz"}},"errors":[],"extensions":{}}`, string(res))
3535
}
3636

3737
func TestHandleRequestRequestJsonBody(t *testing.T) {
3838
s := NewSchema()
3939
err := s.Parse(TestResolveSchemaRequestWithFieldsData{A: TestResolveSchemaRequestWithFieldsDataInnerStruct{Bar: "baz"}}, M{}, nil)
40-
NoError(t, err)
40+
a.NoError(t, err)
4141

4242
query := `
4343
query Foo {
@@ -71,13 +71,13 @@ func TestHandleRequestRequestJsonBody(t *testing.T) {
7171
for _, err := range errs {
7272
panic(err)
7373
}
74-
Equal(t, `{"data":{"a":{"bar":"baz"}},"errors":[],"extensions":{}}`, string(res))
74+
a.Equal(t, `{"data":{"a":{"bar":"baz"}},"errors":[],"extensions":{}}`, string(res))
7575
}
7676

7777
func TestHandleRequestRequestForm(t *testing.T) {
7878
s := NewSchema()
7979
err := s.Parse(TestResolveSchemaRequestWithFieldsData{A: TestResolveSchemaRequestWithFieldsDataInnerStruct{Bar: "baz"}}, M{}, nil)
80-
NoError(t, err)
80+
a.NoError(t, err)
8181

8282
query := `
8383
query Foo {
@@ -115,13 +115,13 @@ func TestHandleRequestRequestForm(t *testing.T) {
115115
for _, err := range errs {
116116
panic(err)
117117
}
118-
Equal(t, `{"data":{"a":{"bar":"baz"}},"errors":[],"extensions":{}}`, string(res))
118+
a.Equal(t, `{"data":{"a":{"bar":"baz"}},"errors":[],"extensions":{}}`, string(res))
119119
}
120120

121121
func TestHandleRequestRequestBatch(t *testing.T) {
122122
s := NewSchema()
123123
err := s.Parse(TestResolveSchemaRequestWithFieldsData{A: TestResolveSchemaRequestWithFieldsDataInnerStruct{Bar: "baz"}}, M{}, nil)
124-
NoError(t, err)
124+
a.NoError(t, err)
125125

126126
query := `
127127
query Foo {
@@ -162,5 +162,5 @@ func TestHandleRequestRequestBatch(t *testing.T) {
162162
for _, err := range errs {
163163
panic(err)
164164
}
165-
Equal(t, `[{"data":{"a":{"bar":"baz"}},"errors":[],"extensions":{}},{"data":{"a":{"foo":null}},"errors":[],"extensions":{}}]`, string(res))
165+
a.Equal(t, `[{"data":{"a":{"bar":"baz"}},"errors":[],"extensions":{}},{"data":{"a":{"foo":null}},"errors":[],"extensions":{}}]`, string(res))
166166
}

inject_schema.go

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,12 @@ func (s *Schema) getDirectives() []qlDirective {
103103

104104
func (s *Schema) getAllQLTypes() []qlType {
105105
if s.graphqlTypesList == nil {
106-
s.graphqlTypesList = make([]qlType, len(s.types)+len(s.inTypes)+len(s.definedEnums)+len(scalars))
106+
// Only generate s.graphqlTypesList once as the content won't change on runtime
107+
108+
s.graphqlTypesList = make(
109+
[]qlType,
110+
len(s.types)+len(s.inTypes)+len(s.definedEnums)+len(scalars)+len(s.interfaces),
111+
)
107112

108113
idx := 0
109114
for _, type_ := range s.types {
@@ -124,6 +129,11 @@ func (s *Schema) getAllQLTypes() []qlType {
124129
s.graphqlTypesList[idx] = scalar
125130
idx++
126131
}
132+
for _, interface_ := range s.interfaces {
133+
obj, _ := s.objToQLType(interface_)
134+
s.graphqlTypesList[idx] = *obj
135+
idx++
136+
}
127137

128138
sort.Slice(s.graphqlTypesList, func(a int, b int) bool { return *s.graphqlTypesList[a].Name < *s.graphqlTypesList[b].Name })
129139
}
@@ -267,6 +277,14 @@ func (s *Schema) objToQLType(item *obj) (res *qlType, isNonNull bool) {
267277
return s.objToQLType(s.types[item.typeName])
268278
case valueTypeObj:
269279
isNonNull = true
280+
interfaces := []qlType{}
281+
if len(item.implementations) != 0 {
282+
for _, implementation := range item.implementations {
283+
interface_, _ := s.objToQLType(implementation)
284+
interfaces = append(interfaces, *interface_)
285+
}
286+
}
287+
270288
res = &qlType{
271289
Kind: typeKindObject,
272290
Name: &item.typeName,
@@ -290,7 +308,7 @@ func (s *Schema) objToQLType(item *obj) (res *qlType, isNonNull bool) {
290308
s.graphqlObjFields[item.typeName] = res
291309
return res
292310
},
293-
Interfaces: []qlType{},
311+
Interfaces: interfaces,
294312
}
295313
return
296314
case valueTypeEnum:
@@ -307,6 +325,46 @@ func (s *Schema) objToQLType(item *obj) (res *qlType, isNonNull bool) {
307325
isNonNull = false
308326
}
309327
return
328+
case valueTypeInterfaceRef:
329+
return s.objToQLType(s.interfaces[item.typeName])
330+
case valueTypeInterface:
331+
// A interface should be non null BUT as a interface in go can be nil we set it to false
332+
isNonNull = false
333+
334+
res = &qlType{
335+
Kind: typeKindInterface,
336+
Name: &item.typeName,
337+
Description: h.PtrToEmptyStr,
338+
Interfaces: []qlType{},
339+
PossibleTypes: func() []qlType {
340+
possibleTypes := make([]qlType, len(item.implementations))
341+
for idx, implementation := range item.implementations {
342+
item, _ := s.objToQLType(implementation)
343+
possibleTypes[idx] = *item
344+
}
345+
return possibleTypes
346+
},
347+
Fields: func(args isDeprecatedArgs) []qlField {
348+
fields, ok := s.graphqlObjFields[item.typeName]
349+
if ok {
350+
return fields
351+
}
352+
353+
res := []qlField{}
354+
for _, innerItem := range item.objContents {
355+
res = append(res, qlField{
356+
Name: string(innerItem.qlFieldName),
357+
Args: s.getObjectArgs(innerItem),
358+
Type: *wrapQLTypeInNonNull(s.objToQLType(innerItem)),
359+
})
360+
}
361+
sort.Slice(res, func(a int, b int) bool { return res[a].Name < res[b].Name })
362+
363+
s.graphqlObjFields[item.typeName] = res
364+
return res
365+
},
366+
}
367+
return
310368
default:
311369
return resolveObjToScalar(item), true
312370
}

0 commit comments

Comments
 (0)