enisdenjo / graphql-composite

MIT License
5 stars 0 forks source link

Nullable vs Non-Nullable output types in union members fields #31

Closed kamilkisiela closed 2 months ago

kamilkisiela commented 3 months ago

https://federation-compatibility.the-guild.dev/entity-and-no-entity/subgraphs

A

extend schema
  @link(
    url: "https://specs.apollo.dev/federation/v2.3"
    import: ["@shareable"]
  )

type User {
  id: ID @shareable
}

B

extend schema
  @link(
    url: "https://specs.apollo.dev/federation/v2.3"
    import: ["@shareable", "@key"]
  )

union Account = User | Admin

type User @key(fields: "id") {
  id: ID!
  name: String
}

type Admin {
  id: ID
  photo: String @shareable
}

type Query {
  accounts: [Account!]!
}

Given these two subgraphs, when running the following query:

{
  accounts {
    ... on User {
      id
      name
    }
    ... on Admin {
      id
      photo
    }
  }
}

We hit a GraphQL error because User.id has a non-nullable output type ID!, but Admin.id resolves ID.

I think the only solution here is to apply an alias on conflicting fields.

kamilkisiela commented 3 months ago

The least intrusive (least amount of aliasing) method I could think of would be to compare selection sets of GatherPlanResolver, as it is really the only place where the type collision can happen.

We need to detect fields that are not mergeable (according to https://spec.graphql.org/draft/#FieldsInSetCanMerge()).

For that we need to store types of fields, like types: { [<subgraph name>]: <type as string>}.

Once we find them, we apply aliases. We could adopt some logic here, like to leave the first field occurrence untouched, but rename every other field that has a different output type, or we could pick the one that is used the least. Honestly, I don't think it matters that much... so we could apply a simple algorithm here (alias fields based on type, basically group them).


  1. Include those temporary aliases in the exports (either during their creation or afterwards, will see). This is important as we want to make the gather plan cacheable / able to be persisted.
  2. Populate the result with the export data, but with the correct property/path.