-
Notifications
You must be signed in to change notification settings - Fork 3
Description
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.