Add support for interfaces, part 1: the simplest cases (#52)
## Summary: In this commit I begin the journey to add the long-awaited support for interfaces (part of #8). Well, it's not the beginning: I already had some half-written broken code around. But it's the first fully functional support, and especially, the first *tested* support; it's probably best to review the nontrivially-changed code as if it were new. Conceptually, the code so far is pretty simple: we generate an interface type, and the implementations. (That code is in fact mostly unchanged.) The complexity comes in because encoding/json doesn't know how to unmarshal that. So we have to add an UnmarshalJSON method, which actually has to be on the types with interface-type fields, that knows how. I factored it into two methods, such that that UnmarshalJSON method is just glue, and then there's a separate function, corresponding to each interface-type, that actually does all the work. (If only one could just write it as an actual method!) The method uses the same trick suggested to me by a few others in another context to deserialize all but one field, then handle that field specially, which is discussed in the code. This still has some limitations, which will be lifted in future commits: - it doesn't allow for list-of-interface fields - it requires that you manually ask for `__typename` - it doesn't support fragments, i.e. you can only query for interface fields, not concrete-type-specific ones But it works, even in integration tests, which is progress! As a part of this, I added a proper config option for the "allow broken features" flag, since I need to be able to set it from the integration tests which are in a separate package (and actually shell out via `go generate`). I also renamed what was to be the first case (InterfaceNoFragments), and replaced it with a further-simplified version (avoiding list-of-interface fields. [1] https://github.com/benjaminjkraft/notes/blob/master/go-json-interfaces.md Issue: https://github.com/Khan/genqlient/issues/8 ## Test plan: make tesc Author: benjaminjkraft Reviewers: dnerdy, benjaminjkraft, aberkan, csilvers, 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/52
This commit is contained in:
@@ -4,10 +4,98 @@ package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
)
|
||||
|
||||
// queryWithInterfaceNoFragmentsBeing includes the requested fields of the GraphQL type Being.
|
||||
type queryWithInterfaceNoFragmentsBeing interface {
|
||||
implementsGraphQLInterfacequeryWithInterfaceNoFragmentsBeing()
|
||||
}
|
||||
|
||||
func (v *queryWithInterfaceNoFragmentsBeingUser) implementsGraphQLInterfacequeryWithInterfaceNoFragmentsBeing() {
|
||||
}
|
||||
func (v *queryWithInterfaceNoFragmentsBeingAnimal) implementsGraphQLInterfacequeryWithInterfaceNoFragmentsBeing() {
|
||||
}
|
||||
|
||||
func __unmarshalqueryWithInterfaceNoFragmentsBeing(v *queryWithInterfaceNoFragmentsBeing, 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(queryWithInterfaceNoFragmentsBeingUser)
|
||||
return json.Unmarshal(m, *v)
|
||||
case "Animal":
|
||||
*v = new(queryWithInterfaceNoFragmentsBeingAnimal)
|
||||
return json.Unmarshal(m, *v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
`Unexpected concrete type for queryWithInterfaceNoFragmentsBeing: "%v"`, tn.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// queryWithInterfaceNoFragmentsBeingAnimal includes the requested fields of the GraphQL type Animal.
|
||||
type queryWithInterfaceNoFragmentsBeingAnimal struct {
|
||||
Typename string `json:"__typename"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// queryWithInterfaceNoFragmentsBeingUser includes the requested fields of the GraphQL type User.
|
||||
type queryWithInterfaceNoFragmentsBeingUser struct {
|
||||
Typename string `json:"__typename"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// queryWithInterfaceNoFragmentsMeUser includes the requested fields of the GraphQL type User.
|
||||
type queryWithInterfaceNoFragmentsMeUser struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// queryWithInterfaceNoFragmentsResponse is returned by queryWithInterfaceNoFragments on success.
|
||||
type queryWithInterfaceNoFragmentsResponse struct {
|
||||
Being queryWithInterfaceNoFragmentsBeing `json:"-"`
|
||||
Me queryWithInterfaceNoFragmentsMeUser `json:"me"`
|
||||
}
|
||||
|
||||
func (v *queryWithInterfaceNoFragmentsResponse) UnmarshalJSON(b []byte) error {
|
||||
|
||||
type queryWithInterfaceNoFragmentsResponseWrapper queryWithInterfaceNoFragmentsResponse
|
||||
|
||||
var firstPass struct {
|
||||
*queryWithInterfaceNoFragmentsResponseWrapper
|
||||
Being json.RawMessage `json:"being"`
|
||||
}
|
||||
firstPass.queryWithInterfaceNoFragmentsResponseWrapper = (*queryWithInterfaceNoFragmentsResponseWrapper)(v)
|
||||
|
||||
err := json.Unmarshal(b, &firstPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = __unmarshalqueryWithInterfaceNoFragmentsBeing(
|
||||
&v.Being, firstPass.Being)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// queryWithVariablesResponse is returned by queryWithVariables on success.
|
||||
type queryWithVariablesResponse struct {
|
||||
User queryWithVariablesUser `json:"user"`
|
||||
@@ -82,3 +170,35 @@ query queryWithVariables ($id: ID!) {
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
func queryWithInterfaceNoFragments(
|
||||
ctx context.Context,
|
||||
client graphql.Client,
|
||||
id string,
|
||||
) (*queryWithInterfaceNoFragmentsResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"id": id,
|
||||
}
|
||||
|
||||
var retval queryWithInterfaceNoFragmentsResponse
|
||||
err := client.MakeRequest(
|
||||
ctx,
|
||||
"queryWithInterfaceNoFragments",
|
||||
`
|
||||
query queryWithInterfaceNoFragments ($id: ID!) {
|
||||
being(id: $id) {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
me {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ schema: schema.graphql
|
||||
operations:
|
||||
- "*_test.go"
|
||||
generated: generated.go
|
||||
allow_broken_features: true
|
||||
|
||||
@@ -56,6 +56,49 @@ func TestVariables(t *testing.T) {
|
||||
assert.Zero(t, resp.User)
|
||||
}
|
||||
|
||||
func TestInterfaceNoFragments(t *testing.T) {
|
||||
_ = `# @genqlient
|
||||
query queryWithInterfaceNoFragments($id: ID!) {
|
||||
being(id: $id) { __typename id name }
|
||||
me { id name }
|
||||
}`
|
||||
|
||||
ctx := context.Background()
|
||||
server := server.RunServer()
|
||||
defer server.Close()
|
||||
client := graphql.NewClient(server.URL, http.DefaultClient)
|
||||
|
||||
resp, err := queryWithInterfaceNoFragments(ctx, client, "1")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "1", resp.Me.Id)
|
||||
assert.Equal(t, "Yours Truly", resp.Me.Name)
|
||||
|
||||
user, ok := resp.Being.(*queryWithInterfaceNoFragmentsBeingUser)
|
||||
require.Truef(t, ok, "got %T, not User", resp.Being)
|
||||
assert.Equal(t, "1", user.Id)
|
||||
assert.Equal(t, "Yours Truly", user.Name)
|
||||
|
||||
resp, err = queryWithInterfaceNoFragments(ctx, client, "3")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "1", resp.Me.Id)
|
||||
assert.Equal(t, "Yours Truly", resp.Me.Name)
|
||||
|
||||
animal, ok := resp.Being.(*queryWithInterfaceNoFragmentsBeingAnimal)
|
||||
require.Truef(t, ok, "got %T, not Animal", resp.Being)
|
||||
assert.Equal(t, "3", animal.Id)
|
||||
assert.Equal(t, "Fido", animal.Name)
|
||||
|
||||
resp, err = queryWithInterfaceNoFragments(ctx, client, "4757233945723")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "1", resp.Me.Id)
|
||||
assert.Equal(t, "Yours Truly", resp.Me.Name)
|
||||
|
||||
assert.Nil(t, resp.Being)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
type Query {
|
||||
me: User
|
||||
user(id: ID!): User
|
||||
being(id: ID!): Being
|
||||
}
|
||||
|
||||
type User {
|
||||
type User implements Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
luckyNumber: Int
|
||||
}
|
||||
|
||||
type Animal implements Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
species: Species!
|
||||
owner: Being
|
||||
}
|
||||
|
||||
enum Species {
|
||||
DOG
|
||||
COELACANTH
|
||||
}
|
||||
|
||||
interface Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
@@ -40,9 +41,17 @@ type DirectiveRoot struct {
|
||||
}
|
||||
|
||||
type ComplexityRoot struct {
|
||||
Animal struct {
|
||||
ID func(childComplexity int) int
|
||||
Name func(childComplexity int) int
|
||||
Owner func(childComplexity int) int
|
||||
Species func(childComplexity int) int
|
||||
}
|
||||
|
||||
Query struct {
|
||||
Me func(childComplexity int) int
|
||||
User func(childComplexity int, id string) int
|
||||
Being func(childComplexity int, id string) int
|
||||
Me func(childComplexity int) int
|
||||
User func(childComplexity int, id string) int
|
||||
}
|
||||
|
||||
User struct {
|
||||
@@ -55,6 +64,7 @@ type ComplexityRoot struct {
|
||||
type QueryResolver interface {
|
||||
Me(ctx context.Context) (*User, error)
|
||||
User(ctx context.Context, id string) (*User, error)
|
||||
Being(ctx context.Context, id string) (Being, error)
|
||||
}
|
||||
|
||||
type executableSchema struct {
|
||||
@@ -72,6 +82,46 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
_ = ec
|
||||
switch typeName + "." + field {
|
||||
|
||||
case "Animal.id":
|
||||
if e.complexity.Animal.ID == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Animal.ID(childComplexity), true
|
||||
|
||||
case "Animal.name":
|
||||
if e.complexity.Animal.Name == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Animal.Name(childComplexity), true
|
||||
|
||||
case "Animal.owner":
|
||||
if e.complexity.Animal.Owner == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Animal.Owner(childComplexity), true
|
||||
|
||||
case "Animal.species":
|
||||
if e.complexity.Animal.Species == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Animal.Species(childComplexity), true
|
||||
|
||||
case "Query.being":
|
||||
if e.complexity.Query.Being == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Query_being_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.Being(childComplexity, args["id"].(string)), true
|
||||
|
||||
case "Query.me":
|
||||
if e.complexity.Query.Me == nil {
|
||||
break
|
||||
@@ -165,13 +215,31 @@ var sources = []*ast.Source{
|
||||
{Name: "../schema.graphql", Input: `type Query {
|
||||
me: User
|
||||
user(id: ID!): User
|
||||
being(id: ID!): Being
|
||||
}
|
||||
|
||||
type User {
|
||||
type User implements Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
luckyNumber: Int
|
||||
}
|
||||
|
||||
type Animal implements Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
species: Species!
|
||||
owner: Being
|
||||
}
|
||||
|
||||
enum Species {
|
||||
DOG
|
||||
COELACANTH
|
||||
}
|
||||
|
||||
interface Being {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
`, BuiltIn: false},
|
||||
}
|
||||
var parsedSchema = gqlparser.MustLoadSchema(sources...)
|
||||
@@ -195,6 +263,21 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query_being_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
var arg0 string
|
||||
if tmp, ok := rawArgs["id"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
|
||||
arg0, err = ec.unmarshalNID2string(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["id"] = 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{}{}
|
||||
@@ -248,6 +331,143 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg
|
||||
|
||||
// region **************************** field.gotpl *****************************
|
||||
|
||||
func (ec *executionContext) _Animal_id(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.ID, 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.(string)
|
||||
fc.Result = res
|
||||
return ec.marshalNID2string(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Animal_name(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.Name, 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.(string)
|
||||
fc.Result = res
|
||||
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Animal_species(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.Species, 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.(Species)
|
||||
fc.Result = res
|
||||
return ec.marshalNSpecies2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐSpecies(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Animal_owner(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.Owner, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(Being)
|
||||
fc.Result = res
|
||||
return ec.marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(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 {
|
||||
@@ -319,6 +539,45 @@ func (ec *executionContext) _Query_user(ctx context.Context, field graphql.Colle
|
||||
return ec.marshalOUser2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUser(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query_being(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_being_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().Being(rctx, args["id"].(string))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(Being)
|
||||
fc.Result = res
|
||||
return ec.marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(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 {
|
||||
@@ -1583,10 +1842,72 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co
|
||||
|
||||
// region ************************** interface.gotpl ***************************
|
||||
|
||||
func (ec *executionContext) _Being(ctx context.Context, sel ast.SelectionSet, obj Being) 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)
|
||||
case Animal:
|
||||
return ec._Animal(ctx, sel, &obj)
|
||||
case *Animal:
|
||||
if obj == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._Animal(ctx, sel, obj)
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected type %T", obj))
|
||||
}
|
||||
}
|
||||
|
||||
// endregion ************************** interface.gotpl ***************************
|
||||
|
||||
// region **************************** object.gotpl ****************************
|
||||
|
||||
var animalImplementors = []string{"Animal", "Being"}
|
||||
|
||||
func (ec *executionContext) _Animal(ctx context.Context, sel ast.SelectionSet, obj *Animal) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, animalImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
var invalids uint32
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("Animal")
|
||||
case "id":
|
||||
out.Values[i] = ec._Animal_id(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "name":
|
||||
out.Values[i] = ec._Animal_name(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "species":
|
||||
out.Values[i] = ec._Animal_species(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "owner":
|
||||
out.Values[i] = ec._Animal_owner(ctx, field, obj)
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch()
|
||||
if invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var queryImplementors = []string{"Query"}
|
||||
|
||||
func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
|
||||
@@ -1624,6 +1945,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
||||
res = ec._Query_user(ctx, field)
|
||||
return res
|
||||
})
|
||||
case "being":
|
||||
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_being(ctx, field)
|
||||
return res
|
||||
})
|
||||
case "__type":
|
||||
out.Values[i] = ec._Query___type(ctx, field)
|
||||
case "__schema":
|
||||
@@ -1639,7 +1971,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
||||
return out
|
||||
}
|
||||
|
||||
var userImplementors = []string{"User"}
|
||||
var userImplementors = []string{"User", "Being"}
|
||||
|
||||
func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *User) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, userImplementors)
|
||||
@@ -1948,6 +2280,16 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec
|
||||
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)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNSpecies2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐSpecies(ctx context.Context, sel ast.SelectionSet, v Species) graphql.Marshaler {
|
||||
return v
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) {
|
||||
res, err := graphql.UnmarshalString(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
@@ -2192,6 +2534,13 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a
|
||||
return res
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalOBeing2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐBeing(ctx context.Context, sel ast.SelectionSet, v Being) graphql.Marshaler {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._Being(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)
|
||||
|
||||
@@ -2,8 +2,70 @@
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Being interface {
|
||||
IsBeing()
|
||||
}
|
||||
|
||||
type Animal struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Species Species `json:"species"`
|
||||
Owner Being `json:"owner"`
|
||||
}
|
||||
|
||||
func (Animal) IsBeing() {}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LuckyNumber *int `json:"luckyNumber"`
|
||||
}
|
||||
|
||||
func (User) IsBeing() {}
|
||||
|
||||
type Species string
|
||||
|
||||
const (
|
||||
SpeciesDog Species = "DOG"
|
||||
SpeciesCoelacanth Species = "COELACANTH"
|
||||
)
|
||||
|
||||
var AllSpecies = []Species{
|
||||
SpeciesDog,
|
||||
SpeciesCoelacanth,
|
||||
}
|
||||
|
||||
func (e Species) IsValid() bool {
|
||||
switch e {
|
||||
case SpeciesDog, SpeciesCoelacanth:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Species) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *Species) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = Species(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid Species", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Species) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ var users = []*User{
|
||||
{ID: "2", Name: "Raven", LuckyNumber: intptr(-1)},
|
||||
}
|
||||
|
||||
var animals = []*Animal{
|
||||
{ID: "3", Name: "Fido", Species: SpeciesDog, Owner: userByID("0")},
|
||||
{ID: "4", Name: "Old One", Species: SpeciesCoelacanth, Owner: nil},
|
||||
}
|
||||
|
||||
func userByID(id string) *User {
|
||||
for _, user := range users {
|
||||
if id == user.ID {
|
||||
@@ -24,6 +29,20 @@ func userByID(id string) *User {
|
||||
return nil
|
||||
}
|
||||
|
||||
func beingByID(id string) Being {
|
||||
for _, user := range users {
|
||||
if id == user.ID {
|
||||
return user
|
||||
}
|
||||
}
|
||||
for _, animal := range animals {
|
||||
if id == animal.ID {
|
||||
return animal
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Me(ctx context.Context) (*User, error) {
|
||||
return userByID("1"), nil
|
||||
}
|
||||
@@ -32,6 +51,10 @@ func (r *queryResolver) User(ctx context.Context, id string) (*User, error) {
|
||||
return userByID(id), nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Being(ctx context.Context, id string) (Being, error) {
|
||||
return beingByID(id), nil
|
||||
}
|
||||
|
||||
func RunServer() *httptest.Server {
|
||||
gqlgenServer := handler.New(NewExecutableSchema(Config{Resolvers: &resolver{}}))
|
||||
gqlgenServer.AddTransport(transport.POST{})
|
||||
|
||||
Reference in New Issue
Block a user