granate icon indicating copy to clipboard operation
granate copied to clipboard

granate (relay part) gets confused by similar types

Open nicolaiskogheim opened this issue 7 years ago • 0 comments

When graphql type A is a subset of B, then B can be "converted" to A, and look like an A in the graphql response to a query. This is because graphql types is represented as interfaces in go, and it is a feature that a type struct b {} can act as both an A and a B if it satisfies both those interfaces.

Let me explain.

Graphql schema:

type Admin extends Node {
	id: ID!
	name: String!
}

type User extends Node {
	id: ID!
	name: String!
	userSpecific: Bool
}

From this, granate generates interfaces:

// schema/adapters.go
type AdminInterface interface {
	IdField(context.Context) (*string, error)
	NameField(context.Context) (*string, error)
}

type UserInterface interface {
	IdField(context.Context) (*string, error)
	NameField(context.Context) (*string, error)
	UserSpecificField(context.Context) (*string, error)
}

The important thing here is that anything that satisfies UserInterface also satisfies the AdminInterface which tricks the following logic:

// schema/definitions.go
func init() {

	nodeDefinitions = relay.NewNodeDefinitions(relay.NodeDefinitionsConfig{
		IDFetcher: func(id string, info graphql.ResolveInfo, ctx context.Context) (interface{}, error) {
			// ...
		},
		TypeResolve: func(p graphql.ResolveTypeParams) *graphql.Object {
			switch p.Value.(type) {
			case AdminInterface: //< UserInterface satisfies this condition
				return adminDefinition

			case UserInterface:
				return userDefinition

			}

			return nil
		},
	})

This means that this GraphQL request

query get_user {
    node(id:"VXNlcjox") { # id: "User:1"
        id

        ... on User {
            name
            userSpecific
	}
	
	... on Admin {
	    name
	}

    }
}

returns this response

{
  "data": {
    "node": {
      "name": "Jon Jonsen",
      "id": "QWRtaW46MQ==" // id: "Admin:1"
      // no userSpecific field, because this is an Admin now
    }
  }
}

I circumvented the problem by adding a dummy field to the graphql types that fell victim to this .. well, it is a feature, but for our purposes it's a bug.

My new graphql schema:

type Admin extends Node {
	id: ID!
	name: String!
	# Do not consume this field /// Document that this field is not part of api
	isAdmin: Bool
}

type User extends Node {
	id: ID!
	name: String!
	userSpecific: Bool
	# Do not consume this field /// Also here
	isUser: Bool
}

Thanks to @noh4ck for helping me finding the cause and a workaround.

nicolaiskogheim avatar Aug 15 '17 15:08 nicolaiskogheim