Sam Sverko
Sam Sverko

Reputation: 1600

Why do subfields conflict when they are in different interfaces?

I am trying to query a single MongoDB document (trivia) using GraphQL (Apollo Server), but am having trouble with one of the document fields.

LightningRoundQuestion.answer and PictureRoundPicture.answer should return a String, and MultipleChoiceRoundQuestion.answer should return an Int. See the schema:

schema

const typeDefs = gql`
  # ROOT TYPES ==================================================
  type Query {
    trivia(_id: String!): Trivia
  }

  # INTERFACES ==================================================
  interface Round {
    type: String!
    theme: String!
    pointValue: Int!
  }

  type LightningRound implements Round {
    type: String!
    theme: String!
    pointValue: Int!
    questions: [LightningRoundQuestion]
  }

  type MultipleChoiceRound implements Round {
    type: String!
    theme: String!
    pointValue: Int!
    questions: [MultipleChoiceRoundQuestion]
  }

  type PictureRound implements Round {
    type: String!
    theme: String!
    pointValue: Int!
    pictures: [PictureRoundPicture]
  }

  # QUERY TYPES =================================================
  type LightningRoundQuestion {
    question: String!
    answer: String!
  }

  type MultipleChoiceRoundQuestion {
    question: String!
    options: [String!]!
    answer: Int!
  }

  type PictureRoundPicture {
    url: String!
    answer: String!
  }

  type Trivia {
    _id: String!
    createdAt: String!
    triviaId: String!
    triviaPin: String!
    host: String!
    rounds: [Round]!
    tieBreaker: TieBreaker!
  }

  type TieBreaker {
    question: String
    answer: Int
  }
`

resolvers & server

const resolvers = {
  Query: {
    trivia: async (root, { _id }) => {
      return triviaCollection.findOne(ObjectId(_id))
    }
  },
  Round: {
    __resolveType(obj) {
      if (obj.type === 'multipleChoice') {
        return 'MultipleChoiceRound'
      } else if (obj.type === 'lightning') {
        return 'LightningRound'
      } else if (obj.type === 'picture') {
        return 'PictureRound'
      }
    }
  }
}

const server = new ApolloServer({ typeDefs, resolvers })

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`)
})

query

query {
  trivia(_id: "5e827a4e1c9d4400009fea32") {
    _id
    createdAt
    triviaId
    triviaPin
    host
    rounds {
      ... on LightningRound {
        questions {
          question
          answer
        }
      }
      ... on MultipleChoiceRound {
        questions {
          question
          options
          answer
        }
      }
      ... on PictureRound {
        pictures {
          url
          answer
        }
      }
    }
  }
}

I get the error message:

"message": "Fields \"questions\" conflict because subfields \"answer\" conflict because they return conflicting types \"String!\" and \"Int!\". Use different aliases on the fields to fetch both if this was intentional."

Not quite sure what to do next, I looked at Alias in the Apollo documentation, but there's little help there.

Upvotes: 2

Views: 2870

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84687

This is a bit of a gotcha when using abstract types and occurs as a result of how GraphQL handles merging field selections. What it boils down to is that if you request a field with the same name (or alias) multiple times inside the same selection set, the field has to return the same type. In the above schema, it doesn't -- questions could have the type [LightningRoundQuestion], or [PictureRoundPicture] and so on.

For a detailed explanation, see this section of the spec.

There's two workarounds for this. On the client side, you can use aliases to ensure GraphQL won't try to merge the fields in the first place:

rounds {
  ... on LightningRound {
    lightningQuestions: questions {
      question
      answer
    }
  }
  ... on MultipleChoiceRound {
    multipleChoiceQuestions: questions {
      question
      options
      answer
    }
  }
}

This is your best bet when you can't change the schema. However, you can also just change the names of the fields on the server-side for better client-side DX.

rounds {
  ... on LightningRound {
    lightningQuestions {
      question
      answer
    }
  }
  ... on MultipleChoiceRound {
    multipleChoiceQuestions {
      question
      options
      answer
    }
  }
}

Notice that we don't have to do that with, type, theme or pointValue because these fields have the same type across all your implementing types.

Upvotes: 5

Related Questions