Search code examples
graphqltypegraphql

TypeGraphQL - Not able to match all the interfaces of a union


Summary

The goal is to declare the return type of a mutation using a union in order to express multiple states: Success and user errors

Being able to select concrete types according to the use cases:

mutation($data: CreateUserInput!) {
  createUser(data: $data){
    ... on CreateUserSuccess {
      user {
        id
      }
    }
   ... on EmailTakenError {
    emailWasTaken
  }
  ... on UserError {
    code
    message
  }
  }
}
Implementation using TypeGraphQ:
@ObjectType()
class CreateUserSuccess {
  @Field(() => User)
    user: User
}
@ObjectType()
class EmailTakenError {
  @Field()
    emailWasTaken: boolean
}

const mapMutationValueKeyToObjectType = {
  user: CreateUserSuccess,
  code: UserError,
  emailWasTaken: EmailTakenError
}
const CreateUserPayload = createUnionType({
  name: 'CreateUserPayload',
  types: () => [CreateUserSuccess, EmailTakenError, UserError] as const,
  resolveType: mutationValue => {
    const mapperKeys = Object.keys(mapMutationValueKeyToObjectType)
    const mutationValueKey = mapperKeys.find((key) => key in mutationValue)

    return mapMutationValueKeyToObjectType[mutationValueKey]
  }
})

@InputType()
class CreateUserInput implements Partial<User> {
  @Field()
    name: string

  @Field()
    email: string
}

@Resolver(User)
export class UserResolver {
  @Mutation(() => CreateUserPayload)
  createUser (@Arg('data', {
    description: 'Represents the input data needed to create a new user'
  }) createUserInput: CreateUserInput) {
    const { name, email } = createUserInput

    return createUser({ name, email })
  }
}
Data layer
export const createUser = async ({
  name, email
}: { name: string; email: string; }) => {
  const existingUser = await dbClient.user.findUnique({
    where: {
      email
    }
  })

  if (existingUser) {
    return {
      code: ErrorCode.DUPLICATE_ENTRY,
      message: "There's an existing user with the provided email.",
      emailWasTaken: true
    }
  }

  return dbClient.user.create({
    data: {
      name,
      email
    }
  })
}

Issue

The response doesn't resolve all of the selected fields according to their unions, even by returning fields that are related to different types

 if (existingUser) {
    return {
      code: ErrorCode.DUPLICATE_ENTRY,
      message: "There's an existing user with the provided email.",
      emailWasTaken: true
    }
  }

enter image description here

My doubt is this case is, why emailWasTaken is not being returned within the response if the EmailTakenError type is being selected?


Solution

  • This was an interpretation mistake on my part

    The reasoning is that resolvers with a union type as the return definition should indeed just return one of those, in the case above, UserError and EmailTakenError wouldn't be returned on the same response

    More info on this GitHub discussion