Skip to content

granate (relay part) gets confused by similar types #21

@nicolaiskogheim

Description

@nicolaiskogheim

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions