Add support for inline fragments (#65)

## Summary:
In this commit I add support for inline fragments
(`... on MyType { fields }`) to genqlient.  This will make interfaces a
lot more useful!  In future commits I'll add named fragments, for which
we'll generate slightly different types, as discussed in DESIGN.md.

In general, implementing the flattening approach described in DESIGN.md
was... surprisingly easy.  All we have to do is recurse on applicable
fragments when generating our selection-set.  The refactor to
selection-set handling this encouraged was, I think, quite beneficial.
It did reveal two tricky pre-existing issues.

One issue is that GraphQL allows for duplicate selections, as long as
they match.  (In practice, this is only useful in the context of
fragments, although GraphQL allows it even without.) I decided to handle
the simple case (duplicate leaf fields; we just deduplicate) but leave
to the future the complex cases where we need to merge different
sub-selections (now #64).  For now we just forbid that; we can see how
much it comes up.

The other issue is that we are generating type-names incorrectly for
interface types; I had intended to do `MyInterfaceMyFieldMyType` for
shared fields and `MyImplMyFieldMyType` for non-shared ones, but instead
I did `MyFieldMyType`, which is inconsistent already and can result in
conflicts in the presence of fragments.  I'm going to fix this in a
separate commit, though, because it's going to require some refactoring
and is irrelevant to the main logic of this commit; I left some TODOs in
the tests related to this.

Issue: https://github.com/Khan/genqlient/issues/8

## Test plan:
make check


Author: benjaminjkraft

Reviewers: dnerdy, aberkan, MiguelCastillo

Required Reviewers: 

Approved by: dnerdy

Checks:  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Test (1.13),  Lint,  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Test (1.13),  Lint

Pull request URL: https://github.com/Khan/genqlient/pull/65
This commit is contained in:
Ben Kraft
2021-08-27 18:06:30 -07:00
committed by GitHub
parent e99dced757
commit e6b1984d44
19 changed files with 2270 additions and 66 deletions
+303
View File
@@ -10,6 +10,253 @@ import (
"github.com/Khan/genqlient/graphql"
)
type Species string
const (
SpeciesDog Species = "DOG"
SpeciesCoelacanth Species = "COELACANTH"
)
// queryWithFragmentsBeingsAnimal includes the requested fields of the GraphQL type Animal.
type queryWithFragmentsBeingsAnimal struct {
Typename string `json:"__typename"`
Id string `json:"id"`
Name string `json:"name"`
Hair queryWithFragmentsBeingsHair `json:"hair"`
Species Species `json:"species"`
Owner queryWithFragmentsBeingsOwnerBeing `json:"-"`
}
func (v *queryWithFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error {
type queryWithFragmentsBeingsAnimalWrapper queryWithFragmentsBeingsAnimal
var firstPass struct {
*queryWithFragmentsBeingsAnimalWrapper
Owner json.RawMessage `json:"owner"`
}
firstPass.queryWithFragmentsBeingsAnimalWrapper = (*queryWithFragmentsBeingsAnimalWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Owner
raw := firstPass.Owner
err = __unmarshalqueryWithFragmentsBeingsOwnerBeing(
target, raw)
if err != nil {
return err
}
}
return nil
}
// queryWithFragmentsBeingsBeing includes the requested fields of the GraphQL interface Being.
//
// queryWithFragmentsBeingsBeing is implemented by the following types:
// queryWithFragmentsBeingsUser
// queryWithFragmentsBeingsAnimal
//
// The GraphQL type's documentation follows.
//
//
type queryWithFragmentsBeingsBeing interface {
implementsGraphQLInterfacequeryWithFragmentsBeingsBeing()
// GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values).
GetTypename() string
// GetId returns the interface-field "id" from its implementation.
GetId() string
// GetName returns the interface-field "name" from its implementation.
GetName() string
}
func (v *queryWithFragmentsBeingsUser) implementsGraphQLInterfacequeryWithFragmentsBeingsBeing() {}
// GetTypename is a part of, and documented with, the interface queryWithFragmentsBeingsBeing.
func (v *queryWithFragmentsBeingsUser) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface queryWithFragmentsBeingsBeing.
func (v *queryWithFragmentsBeingsUser) GetId() string { return v.Id }
// GetName is a part of, and documented with, the interface queryWithFragmentsBeingsBeing.
func (v *queryWithFragmentsBeingsUser) GetName() string { return v.Name }
func (v *queryWithFragmentsBeingsAnimal) implementsGraphQLInterfacequeryWithFragmentsBeingsBeing() {}
// GetTypename is a part of, and documented with, the interface queryWithFragmentsBeingsBeing.
func (v *queryWithFragmentsBeingsAnimal) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface queryWithFragmentsBeingsBeing.
func (v *queryWithFragmentsBeingsAnimal) GetId() string { return v.Id }
// GetName is a part of, and documented with, the interface queryWithFragmentsBeingsBeing.
func (v *queryWithFragmentsBeingsAnimal) GetName() string { return v.Name }
func __unmarshalqueryWithFragmentsBeingsBeing(v *queryWithFragmentsBeingsBeing, m json.RawMessage) error {
if string(m) == "null" {
return nil
}
var tn struct {
TypeName string `json:"__typename"`
}
err := json.Unmarshal(m, &tn)
if err != nil {
return err
}
switch tn.TypeName {
case "User":
*v = new(queryWithFragmentsBeingsUser)
return json.Unmarshal(m, *v)
case "Animal":
*v = new(queryWithFragmentsBeingsAnimal)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for queryWithFragmentsBeingsBeing: "%v"`, tn.TypeName)
}
}
// queryWithFragmentsBeingsHair includes the requested fields of the GraphQL type BeingsHair.
type queryWithFragmentsBeingsHair struct {
HasHair bool `json:"hasHair"`
}
// queryWithFragmentsBeingsOwnerAnimal includes the requested fields of the GraphQL type Animal.
type queryWithFragmentsBeingsOwnerAnimal struct {
Typename string `json:"__typename"`
Id string `json:"id"`
Name string `json:"name"`
}
// queryWithFragmentsBeingsOwnerBeing includes the requested fields of the GraphQL interface Being.
//
// queryWithFragmentsBeingsOwnerBeing is implemented by the following types:
// queryWithFragmentsBeingsOwnerUser
// queryWithFragmentsBeingsOwnerAnimal
//
// The GraphQL type's documentation follows.
//
//
type queryWithFragmentsBeingsOwnerBeing interface {
implementsGraphQLInterfacequeryWithFragmentsBeingsOwnerBeing()
// GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values).
GetTypename() string
// GetId returns the interface-field "id" from its implementation.
GetId() string
// GetName returns the interface-field "name" from its implementation.
GetName() string
}
func (v *queryWithFragmentsBeingsOwnerUser) implementsGraphQLInterfacequeryWithFragmentsBeingsOwnerBeing() {
}
// GetTypename is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing.
func (v *queryWithFragmentsBeingsOwnerUser) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing.
func (v *queryWithFragmentsBeingsOwnerUser) GetId() string { return v.Id }
// GetName is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing.
func (v *queryWithFragmentsBeingsOwnerUser) GetName() string { return v.Name }
func (v *queryWithFragmentsBeingsOwnerAnimal) implementsGraphQLInterfacequeryWithFragmentsBeingsOwnerBeing() {
}
// GetTypename is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing.
func (v *queryWithFragmentsBeingsOwnerAnimal) GetTypename() string { return v.Typename }
// GetId is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing.
func (v *queryWithFragmentsBeingsOwnerAnimal) GetId() string { return v.Id }
// GetName is a part of, and documented with, the interface queryWithFragmentsBeingsOwnerBeing.
func (v *queryWithFragmentsBeingsOwnerAnimal) GetName() string { return v.Name }
func __unmarshalqueryWithFragmentsBeingsOwnerBeing(v *queryWithFragmentsBeingsOwnerBeing, m json.RawMessage) error {
if string(m) == "null" {
return nil
}
var tn struct {
TypeName string `json:"__typename"`
}
err := json.Unmarshal(m, &tn)
if err != nil {
return err
}
switch tn.TypeName {
case "User":
*v = new(queryWithFragmentsBeingsOwnerUser)
return json.Unmarshal(m, *v)
case "Animal":
*v = new(queryWithFragmentsBeingsOwnerAnimal)
return json.Unmarshal(m, *v)
default:
return fmt.Errorf(
`Unexpected concrete type for queryWithFragmentsBeingsOwnerBeing: "%v"`, tn.TypeName)
}
}
// queryWithFragmentsBeingsOwnerUser includes the requested fields of the GraphQL type User.
type queryWithFragmentsBeingsOwnerUser struct {
Typename string `json:"__typename"`
Id string `json:"id"`
Name string `json:"name"`
LuckyNumber int `json:"luckyNumber"`
}
// queryWithFragmentsBeingsUser includes the requested fields of the GraphQL type User.
type queryWithFragmentsBeingsUser struct {
Typename string `json:"__typename"`
Id string `json:"id"`
Name string `json:"name"`
LuckyNumber int `json:"luckyNumber"`
Hair queryWithFragmentsBeingsHair `json:"hair"`
}
// queryWithFragmentsResponse is returned by queryWithFragments on success.
type queryWithFragmentsResponse struct {
Beings []queryWithFragmentsBeingsBeing `json:"-"`
}
func (v *queryWithFragmentsResponse) UnmarshalJSON(b []byte) error {
type queryWithFragmentsResponseWrapper queryWithFragmentsResponse
var firstPass struct {
*queryWithFragmentsResponseWrapper
Beings []json.RawMessage `json:"beings"`
}
firstPass.queryWithFragmentsResponseWrapper = (*queryWithFragmentsResponseWrapper)(v)
err := json.Unmarshal(b, &firstPass)
if err != nil {
return err
}
{
target := &v.Beings
raw := firstPass.Beings
*target = make(
[]queryWithFragmentsBeingsBeing,
len(raw))
for i, raw := range raw {
target := &(*target)[i]
err = __unmarshalqueryWithFragmentsBeingsBeing(
target, raw)
if err != nil {
return err
}
}
}
return nil
}
// queryWithInterfaceListFieldBeingsAnimal includes the requested fields of the GraphQL type Animal.
type queryWithInterfaceListFieldBeingsAnimal struct {
Typename string `json:"__typename"`
@@ -537,3 +784,59 @@ query queryWithInterfaceListPointerField ($ids: [ID!]!) {
)
return &retval, err
}
func queryWithFragments(
ctx context.Context,
client graphql.Client,
ids []string,
) (*queryWithFragmentsResponse, error) {
variables := map[string]interface{}{
"ids": ids,
}
var retval queryWithFragmentsResponse
err := client.MakeRequest(
ctx,
"queryWithFragments",
`
query queryWithFragments ($ids: [ID!]!) {
beings(ids: $ids) {
__typename
id
... on Being {
id
name
}
... on Animal {
id
hair {
hasHair
}
species
owner {
__typename
id
... on Being {
name
}
... on User {
luckyNumber
}
}
}
... on Lucky {
luckyNumber
}
... on User {
hair {
color
}
}
}
}
`,
&retval,
variables,
)
return &retval, err
}
+85
View File
@@ -206,6 +206,91 @@ func TestInterfaceListPointerField(t *testing.T) {
assert.Nil(t, *resp.Beings[2])
}
func TestFragments(t *testing.T) {
_ = `# @genqlient
query queryWithFragments($ids: [ID!]!) {
beings(ids: $ids) {
__typename id
... on Being { id name }
... on Animal {
id
hair { hasHair }
species
owner {
id
... on Being { name }
... on User { luckyNumber }
}
}
... on Lucky { luckyNumber }
... on User { hair { color } }
}
}`
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := graphql.NewClient(server.URL, http.DefaultClient)
resp, err := queryWithFragments(ctx, client, []string{"1", "3", "12847394823"})
require.NoError(t, err)
require.Len(t, resp.Beings, 3)
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// Check fields both via interface and via type-assertion when possible
// User has, in total, the fields: __typename id name luckyNumber.
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
assert.Equal(t, "Yours Truly", resp.Beings[0].GetName())
// (hair and luckyNumber we need to cast for)
user, ok := resp.Beings[0].(*queryWithFragmentsBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
// TODO(benkraft): Uncomment once we fix the interface-field type-naming
// bug that's causing this to get the wrong type (because we end up
// generating two conflicting types).
// assert.Equal(t, "Black", user.Hair.Color)
assert.Equal(t, 17, user.LuckyNumber)
// Animal has, in total, the fields:
// __typename
// id
// species
// owner {
// id
// name
// ... on User { luckyNumber }
// }
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
// (hair, species, and owner.* we have to cast for)
animal, ok := resp.Beings[1].(*queryWithFragmentsBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, SpeciesDog, animal.Species)
assert.True(t, animal.Hair.HasHair)
assert.Equal(t, "1", animal.Owner.GetId())
assert.Equal(t, "Yours Truly", animal.Owner.GetName())
// (luckyNumber we have to cast for, again)
owner, ok := animal.Owner.(*queryWithFragmentsBeingsOwnerUser)
require.Truef(t, ok, "got %T, not User", animal.Owner)
assert.Equal(t, "1", owner.Id)
assert.Equal(t, "Yours Truly", owner.Name)
assert.Equal(t, 17, owner.LuckyNumber)
assert.Nil(t, resp.Beings[2])
}
func TestGeneratedCode(t *testing.T) {
// TODO(benkraft): Check that gqlgen is up to date too. In practice that's
// less likely to be a problem, since it should only change if you update
+12 -1
View File
@@ -3,21 +3,28 @@ type Query {
user(id: ID!): User
being(id: ID!): Being
beings(ids: [ID!]!): [Being]!
lotteryWinner(number: Int!): Lucky
}
type User implements Being {
type User implements Being & Lucky {
id: ID!
name: String!
luckyNumber: Int
hair: Hair
}
type Hair { color: String } # silly name to confuse the name-generator
type Animal implements Being {
id: ID!
name: String!
species: Species!
owner: Being
hair: BeingsHair
}
type BeingsHair { hasHair: Boolean! } # silly name to confuse the name-generator
enum Species {
DOG
COELACANTH
@@ -27,3 +34,7 @@ interface Being {
id: ID!
name: String!
}
interface Lucky {
luckyNumber: Int
}
+372 -6
View File
@@ -43,20 +43,31 @@ type DirectiveRoot struct {
type ComplexityRoot struct {
Animal struct {
Hair func(childComplexity int) int
ID func(childComplexity int) int
Name func(childComplexity int) int
Owner func(childComplexity int) int
Species func(childComplexity int) int
}
BeingsHair struct {
HasHair func(childComplexity int) int
}
Hair struct {
Color func(childComplexity int) int
}
Query struct {
Being func(childComplexity int, id string) int
Beings func(childComplexity int, ids []string) int
Me func(childComplexity int) int
User func(childComplexity int, id string) int
Being func(childComplexity int, id string) int
Beings func(childComplexity int, ids []string) int
LotteryWinner func(childComplexity int, number int) int
Me func(childComplexity int) int
User func(childComplexity int, id string) int
}
User struct {
Hair func(childComplexity int) int
ID func(childComplexity int) int
LuckyNumber func(childComplexity int) int
Name func(childComplexity int) int
@@ -68,6 +79,7 @@ type QueryResolver interface {
User(ctx context.Context, id string) (*User, error)
Being(ctx context.Context, id string) (Being, error)
Beings(ctx context.Context, ids []string) ([]Being, error)
LotteryWinner(ctx context.Context, number int) (Lucky, error)
}
type executableSchema struct {
@@ -85,6 +97,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
_ = ec
switch typeName + "." + field {
case "Animal.hair":
if e.complexity.Animal.Hair == nil {
break
}
return e.complexity.Animal.Hair(childComplexity), true
case "Animal.id":
if e.complexity.Animal.ID == nil {
break
@@ -113,6 +132,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Animal.Species(childComplexity), true
case "BeingsHair.hasHair":
if e.complexity.BeingsHair.HasHair == nil {
break
}
return e.complexity.BeingsHair.HasHair(childComplexity), true
case "Hair.color":
if e.complexity.Hair.Color == nil {
break
}
return e.complexity.Hair.Color(childComplexity), true
case "Query.being":
if e.complexity.Query.Being == nil {
break
@@ -137,6 +170,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Beings(childComplexity, args["ids"].([]string)), true
case "Query.lotteryWinner":
if e.complexity.Query.LotteryWinner == nil {
break
}
args, err := ec.field_Query_lotteryWinner_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.LotteryWinner(childComplexity, args["number"].(int)), true
case "Query.me":
if e.complexity.Query.Me == nil {
break
@@ -156,6 +201,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.User(childComplexity, args["id"].(string)), true
case "User.hair":
if e.complexity.User.Hair == nil {
break
}
return e.complexity.User.Hair(childComplexity), true
case "User.id":
if e.complexity.User.ID == nil {
break
@@ -232,21 +284,28 @@ var sources = []*ast.Source{
user(id: ID!): User
being(id: ID!): Being
beings(ids: [ID!]!): [Being]!
lotteryWinner(number: Int!): Lucky
}
type User implements Being {
type User implements Being & Lucky {
id: ID!
name: String!
luckyNumber: Int
hair: Hair
}
type Hair { color: String } # silly name to confuse the name-generator
type Animal implements Being {
id: ID!
name: String!
species: Species!
owner: Being
hair: BeingsHair
}
type BeingsHair { hasHair: Boolean! } # silly name to confuse the name-generator
enum Species {
DOG
COELACANTH
@@ -256,6 +315,10 @@ interface Being {
id: ID!
name: String!
}
interface Lucky {
luckyNumber: Int
}
`, BuiltIn: false},
}
var parsedSchema = gqlparser.MustLoadSchema(sources...)
@@ -309,6 +372,21 @@ func (ec *executionContext) field_Query_beings_args(ctx context.Context, rawArgs
return args, nil
}
func (ec *executionContext) field_Query_lotteryWinner_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 int
if tmp, ok := rawArgs["number"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("number"))
arg0, err = ec.unmarshalNInt2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["number"] = arg0
return args, nil
}
func (ec *executionContext) field_Query_user_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -499,6 +577,105 @@ func (ec *executionContext) _Animal_owner(ctx context.Context, field graphql.Col
return ec.marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx, field.Selections, res)
}
func (ec *executionContext) _Animal_hair(ctx context.Context, field graphql.CollectedField, obj *Animal) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Animal",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Hair, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*BeingsHair)
fc.Result = res
return ec.marshalOBeingsHair2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeingsHair(ctx, field.Selections, res)
}
func (ec *executionContext) _BeingsHair_hasHair(ctx context.Context, field graphql.CollectedField, obj *BeingsHair) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "BeingsHair",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.HasHair, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Hair_color(ctx context.Context, field graphql.CollectedField, obj *Hair) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Hair",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Color, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_me(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -651,6 +828,45 @@ func (ec *executionContext) _Query_beings(ctx context.Context, field graphql.Col
return ec.marshalNBeing2ᚕgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_lotteryWinner(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Query",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Query_lotteryWinner_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().LotteryWinner(rctx, args["number"].(int))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(Lucky)
fc.Result = res
return ec.marshalOLucky2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐLucky(ctx, field.Selections, res)
}
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -824,6 +1040,38 @@ func (ec *executionContext) _User_luckyNumber(ctx context.Context, field graphql
return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
}
func (ec *executionContext) _User_hair(ctx context.Context, field graphql.CollectedField, obj *User) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "User",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Hair, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*Hair)
fc.Result = res
return ec.marshalOHair2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐHair(ctx, field.Selections, res)
}
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -1938,6 +2186,22 @@ func (ec *executionContext) _Being(ctx context.Context, sel ast.SelectionSet, ob
}
}
func (ec *executionContext) _Lucky(ctx context.Context, sel ast.SelectionSet, obj Lucky) graphql.Marshaler {
switch obj := (obj).(type) {
case nil:
return graphql.Null
case User:
return ec._User(ctx, sel, &obj)
case *User:
if obj == nil {
return graphql.Null
}
return ec._User(ctx, sel, obj)
default:
panic(fmt.Errorf("unexpected type %T", obj))
}
}
// endregion ************************** interface.gotpl ***************************
// region **************************** object.gotpl ****************************
@@ -1970,6 +2234,59 @@ func (ec *executionContext) _Animal(ctx context.Context, sel ast.SelectionSet, o
}
case "owner":
out.Values[i] = ec._Animal_owner(ctx, field, obj)
case "hair":
out.Values[i] = ec._Animal_hair(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var beingsHairImplementors = []string{"BeingsHair"}
func (ec *executionContext) _BeingsHair(ctx context.Context, sel ast.SelectionSet, obj *BeingsHair) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, beingsHairImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("BeingsHair")
case "hasHair":
out.Values[i] = ec._BeingsHair_hasHair(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var hairImplementors = []string{"Hair"}
func (ec *executionContext) _Hair(ctx context.Context, sel ast.SelectionSet, obj *Hair) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, hairImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("Hair")
case "color":
out.Values[i] = ec._Hair_color(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -2043,6 +2360,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
return res
})
case "lotteryWinner":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_lotteryWinner(ctx, field)
return res
})
case "__type":
out.Values[i] = ec._Query___type(ctx, field)
case "__schema":
@@ -2058,7 +2386,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
return out
}
var userImplementors = []string{"User", "Being"}
var userImplementors = []string{"User", "Being", "Lucky"}
func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *User) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, userImplementors)
@@ -2081,6 +2409,8 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
}
case "luckyNumber":
out.Values[i] = ec._User_luckyNumber(ctx, field, obj)
case "hair":
out.Values[i] = ec._User_hair(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -2434,6 +2764,21 @@ func (ec *executionContext) marshalNID2ᚕstringᚄ(ctx context.Context, sel ast
return ret
}
func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) {
res, err := graphql.UnmarshalInt(v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler {
res := graphql.MarshalInt(v)
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
}
return res
}
func (ec *executionContext) unmarshalNSpecies2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐSpecies(ctx context.Context, v interface{}) (Species, error) {
var res Species
err := res.UnmarshalGQL(v)
@@ -2695,6 +3040,13 @@ func (ec *executionContext) marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinte
return ec._Being(ctx, sel, v)
}
func (ec *executionContext) marshalOBeingsHair2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeingsHair(ctx context.Context, sel ast.SelectionSet, v *BeingsHair) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._BeingsHair(ctx, sel, v)
}
func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) {
res, err := graphql.UnmarshalBoolean(v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -2719,6 +3071,13 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast
return graphql.MarshalBoolean(*v)
}
func (ec *executionContext) marshalOHair2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐHair(ctx context.Context, sel ast.SelectionSet, v *Hair) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._Hair(ctx, sel, v)
}
func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v interface{}) (*int, error) {
if v == nil {
return nil, nil
@@ -2734,6 +3093,13 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele
return graphql.MarshalInt(*v)
}
func (ec *executionContext) marshalOLucky2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐLucky(ctx context.Context, sel ast.SelectionSet, v Lucky) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._Lucky(ctx, sel, v)
}
func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) {
res, err := graphql.UnmarshalString(v)
return res, graphql.ErrorOnPath(ctx, err)
+19 -4
View File
@@ -12,22 +12,37 @@ type Being interface {
IsBeing()
}
type Lucky interface {
IsLucky()
}
type Animal struct {
ID string `json:"id"`
Name string `json:"name"`
Species Species `json:"species"`
Owner Being `json:"owner"`
ID string `json:"id"`
Name string `json:"name"`
Species Species `json:"species"`
Owner Being `json:"owner"`
Hair *BeingsHair `json:"hair"`
}
func (Animal) IsBeing() {}
type BeingsHair struct {
HasHair bool `json:"hasHair"`
}
type Hair struct {
Color *string `json:"color"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
LuckyNumber *int `json:"luckyNumber"`
Hair *Hair `json:"hair"`
}
func (User) IsBeing() {}
func (User) IsLucky() {}
type Species string
+24 -5
View File
@@ -8,16 +8,26 @@ import (
"github.com/99designs/gqlgen/graphql/handler/transport"
)
func intptr(v int) *int { return &v }
func strptr(v string) *string { return &v }
func intptr(v int) *int { return &v }
var users = []*User{
{ID: "1", Name: "Yours Truly", LuckyNumber: intptr(17)},
{ID: "2", Name: "Raven", LuckyNumber: intptr(-1)},
{
ID: "1", Name: "Yours Truly", LuckyNumber: intptr(17),
Hair: &Hair{Color: strptr("Black")},
},
{ID: "2", Name: "Raven", LuckyNumber: intptr(-1), Hair: nil},
}
var animals = []*Animal{
{ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("0")},
{ID: "4", Name: "Old One", Species: SpeciesCoelacanth, Owner: nil},
{
ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("1"),
Hair: &BeingsHair{HasHair: true},
},
{
ID: "4", Name: "Old One", Species: SpeciesCoelacanth, Owner: nil,
Hair: &BeingsHair{HasHair: false},
},
}
func userByID(id string) *User {
@@ -63,6 +73,15 @@ func (r *queryResolver) Beings(ctx context.Context, ids []string) ([]Being, erro
return ret, nil
}
func (r *queryResolver) LotteryWinner(ctx context.Context, number int) (Lucky, error) {
for _, user := range users {
if user.LuckyNumber != nil && *user.LuckyNumber == number {
return user, nil
}
}
return nil, nil
}
func RunServer() *httptest.Server {
gqlgenServer := handler.New(NewExecutableSchema(Config{Resolvers: &resolver{}}))
gqlgenServer.AddTransport(transport.POST{})