Graphql-code-generator: Querying interfaces generates wrong typenames

Created on 23 Jul 2020  路  2Comments  路  Source: dotansimha/graphql-code-generator

Describe the bug
Queries for interfaces do not have to correct typename.
If you have an Interface, e.g. Person, which is used by a type, e.g Actor, querying Person will resolve into typenames with Actor.

Actual Output

export type PersonByNameQuery = (
  { __typename?: 'Query' }
  & { Person?: Maybe<Array<Maybe<(
    { __typename?: 'Actor' }
    & Pick<Actor, 'uuid' | 'name'>
  )>>> }
);

Expected Output

export type PersonByNameQuery = (
  { __typename?: 'Query' }
  & { Person?: Maybe<Array<Maybe<(
    { __typename?: 'Person' }
    & Pick<Person, 'uuid' | 'name'>
  )>>> }
);

To Reproduce
Use the schema, operations and config at live-example
Sandbox doesn't seem to work atm.

  1. My GraphQL schema:
schema {
  query: Query
}

interface Person {
    uuid: ID!
    name: String!
}

type Actor implements Person {
    uuid: ID!
    name: String!
}

type Query {
    Person(name: String): [Person]
        @cypher(statement: """
            MATCH
            (a:Person)
            WHERE a.name = $name
            RETURN a
        """)
}
  1. My GraphQL operations:
query PersonByName($payload: String!) {
  Person(name: $payload) {
    uuid
    name
  }
}
  1. My codegen.yml config file:
generates:
  types.ts:
    plugins:
      - typescript
      - typescript-operations

Expected behavior

Environment:

  • @graphql-codegen/latest:

Most helpful comment

Hi @hrzwkstim01. Thanks for opening an issue.

This is working as expected. __typename will always resolve to the name of the actual object type returned at runtime. It can never return the name of an abstract type. So if you have a Person interface that's extended by Actor or Director, when querying a field that returns a Person, the __typename will always be either Actor or Director. The same is true for union types, which are also an abstract type like interfaces.

If you add another type that implements person, you'll see the generated type reflects both possible __typename values, in addition to the fields specific to each type:

export type PersonByNameQuery = (
  { __typename?: 'Query' }
  & { Person?: Maybe<Array<Maybe<(
    { __typename?: 'Actor' }
    & Pick<Actor, 'uuid' | 'name'>
  ) | (
    { __typename?: 'Director' }
    & Pick<Director, 'uuid' | 'name'>
  )>>> }
);

All 2 comments

Hi @hrzwkstim01. Thanks for opening an issue.

This is working as expected. __typename will always resolve to the name of the actual object type returned at runtime. It can never return the name of an abstract type. So if you have a Person interface that's extended by Actor or Director, when querying a field that returns a Person, the __typename will always be either Actor or Director. The same is true for union types, which are also an abstract type like interfaces.

If you add another type that implements person, you'll see the generated type reflects both possible __typename values, in addition to the fields specific to each type:

export type PersonByNameQuery = (
  { __typename?: 'Query' }
  & { Person?: Maybe<Array<Maybe<(
    { __typename?: 'Actor' }
    & Pick<Actor, 'uuid' | 'name'>
  ) | (
    { __typename?: 'Director' }
    & Pick<Director, 'uuid' | 'name'>
  )>>> }
);

@danielrearden
Thanks for the rapid answer and explanation!

I was expecting that abstracted classes can also be resolved.
For instance, the neo4j-graphql library is treating queries/mutaiton with interfaces the same as with types, resulting to have Actor or Director nodes also to have a label of Person.
That way I can easily query for the union of Actors and Directors via Person.

That happens if two instances have different views of abstraction 馃槃
Cheers

Was this page helpful?
0 / 5 - 0 ratings