Skip to content

Commit

Permalink
Merge pull request #634 from 99designs/fallback-to-string
Browse files Browse the repository at this point in the history
Use graphql.String for types wrapping a basic string
  • Loading branch information
vektah committed Mar 18, 2019
2 parents fc05501 + a2cce0d commit 3e39b57
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 45 deletions.
95 changes: 57 additions & 38 deletions codegen/config/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,11 @@ func (b *Binder) PointerTo(ref *TypeReference) *TypeReference {
newRef := &TypeReference{
GO: types.NewPointer(ref.GO),
GQL: ref.GQL,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}

b.References = append(b.References, newRef)
Expand All @@ -172,28 +174,34 @@ type TypeReference struct {
Definition *ast.Definition
GQL *ast.Type
GO types.Type
CastType types.Type // Before calling marshalling functions cast from/to this base type
Marshaler *types.Func // When using external marshalling functions this will point to the Marshal function
Unmarshaler *types.Func // When using external marshalling functions this will point to the Unmarshal function
IsMarshaler bool // Does the type implement graphql.Marshaler and graphql.Unmarshaler
}

func (ref *TypeReference) Elem() *TypeReference {
if p, isPtr := ref.GO.(*types.Pointer); isPtr {
return &TypeReference{
GO: p.Elem(),
GQL: ref.GQL,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}
}

if s, isSlice := ref.GO.(*types.Slice); isSlice {
return &TypeReference{
GO: s.Elem(),
GQL: ref.GQL.Elem,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}
}
return nil
Expand Down Expand Up @@ -249,44 +257,6 @@ func (t *TypeReference) HasIsZero() bool {
return false
}

func (t *TypeReference) SelfMarshalling() bool {
it := t.GO
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return false
}

for i := 0; i < namedType.NumMethods(); i++ {
switch namedType.Method(i).Name() {
case "MarshalGQL":
return true
}
}
return false
}

func (t *TypeReference) SelfUnmarshalling() bool {
it := t.GO
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return false
}

for i := 0; i < namedType.NumMethods(); i++ {
switch namedType.Method(i).Name() {
case "UnmarshalGQL":
return true
}
}
return false
}

func (t *TypeReference) UniquenessKey() string {
var nullability = "O"
if t.GQL.NonNull {
Expand Down Expand Up @@ -395,6 +365,22 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
ref.GO = fun.Type().(*types.Signature).Params().At(0).Type()
ref.Marshaler = fun
ref.Unmarshaler = types.NewFunc(0, fun.Pkg(), "Unmarshal"+typeName, nil)
} else if hasMethod(obj.Type(), "MarshalGQL") && hasMethod(obj.Type(), "UnmarshalGQL") {
ref.GO = obj.Type()
ref.IsMarshaler = true
} else if underlying := basicUnderlying(obj.Type()); underlying != nil && underlying.Kind() == types.String {
// Special case for named types wrapping strings. Used by default enum implementations.

ref.GO = obj.Type()
ref.CastType = underlying

underlyingRef, err := b.TypeReference(&ast.Type{NamedType: "String"}, nil)
if err != nil {
return nil, err
}

ref.Marshaler = underlyingRef.Marshaler
ref.Unmarshaler = underlyingRef.Unmarshaler
} else {
ref.GO = obj.Type()
}
Expand Down Expand Up @@ -430,3 +416,36 @@ func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type {

return base
}

func hasMethod(it types.Type, name string) bool {
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return false
}

for i := 0; i < namedType.NumMethods(); i++ {
if namedType.Method(i).Name() == name {
return true
}
}
return false
}

func basicUnderlying(it types.Type) *types.Basic {
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return nil
}

if basic, ok := namedType.Underlying().(*types.Basic); ok {
return basic
}

return nil
}
95 changes: 95 additions & 0 deletions codegen/testserver/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codegen/testserver/gqlgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@ models:
oneFoo: { fieldName: foo }
twoFoo: { fieldName: foo }
oldFoo: { fieldName: foo, resolver: true }
FallbackToStringEncoding:
model: "github.com/99designs/gqlgen/codegen/testserver.FallbackToStringEncoding"
8 changes: 8 additions & 0 deletions codegen/testserver/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,11 @@ type OverlappingFields struct {
Foo int
NewFoo int
}

type FallbackToStringEncoding string

const (
FallbackToStringEncodingA FallbackToStringEncoding = "A"
FallbackToStringEncodingB FallbackToStringEncoding = "B"
FallbackToStringEncodingC FallbackToStringEncoding = "C"
)
3 changes: 3 additions & 0 deletions codegen/testserver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ func (r *queryResolver) DefaultScalar(ctx context.Context, arg string) (string,
func (r *queryResolver) Slices(ctx context.Context) (*Slices, error) {
panic("not implemented")
}
func (r *queryResolver) Fallback(ctx context.Context, arg FallbackToStringEncoding) (FallbackToStringEncoding, error) {
panic("not implemented")
}
func (r *queryResolver) OptionalUnion(ctx context.Context) (TestUnion, error) {
panic("not implemented")
}
Expand Down
4 changes: 4 additions & 0 deletions codegen/testserver/stub.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions codegen/testserver/typefallback.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extend type Query {
fallback(arg: FallbackToStringEncoding!): FallbackToStringEncoding!
}

enum FallbackToStringEncoding {
A
B
C
}
30 changes: 30 additions & 0 deletions codegen/testserver/typefallback_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package testserver

import (
"context"
"net/http/httptest"
"testing"

"github.com/99designs/gqlgen/client"
"github.com/99designs/gqlgen/handler"
"github.com/stretchr/testify/require"
)

func TestTypeFallback(t *testing.T) {
resolvers := &Stub{}

srv := httptest.NewServer(handler.GraphQL(NewExecutableSchema(Config{Resolvers: resolvers})))
c := client.New(srv.URL)

resolvers.QueryResolver.Fallback = func(ctx context.Context, arg FallbackToStringEncoding) (FallbackToStringEncoding, error) {
return arg, nil
}

t.Run("fallback to string passthrough", func(t *testing.T) {
var resp struct {
Fallback string
}
c.MustPost(`query { fallback(arg: A) }`, &resp)
require.Equal(t, "A", resp.Fallback)
})
}

0 comments on commit 3e39b57

Please sign in to comment.