eddeee888 / graphql-code-generator-plugins

List of GraphQL Code Generator plugins that complements the official plugins.
MIT License
44 stars 10 forks source link

[BUG] Mappers not applied to GraphQL union types #233

Open lensbart opened 4 months ago

lensbart commented 4 months ago

Describe the bug For some types, the mapper I define get ignored. I’ve had this happen with two types, both of which are union types, so I guess it’s related to that.

I hope the reduced schema below reproduces it. The schema uses Relay-style connections, but I think that’s unrelated.

The problem is that I don’t want to return someField from the parent resolver. Instead, I want to be forced to return it from its own resolver. In order to do so, it is my understanding that I either have to export UInterfaceMapper, or AMapper, BMapper and CMapper (or all of them) from my mappers file. When doing so, I don’t notice any difference, since the generated types don’t pick up these mappers for the union type U.

I only have 1 mapper file, and the other mapper types get picked up without any issue.

union U = A | B | C

interface UInterface {
    someField(after: String, first: NonNegativeInt!): [String!]!
}

type A implements UInterface {
    someField(after: String, first: NonNegativeInt!): [String!]!
    # ... other fields
}
type B implements UInterface {
    someField(after: String, first: NonNegativeInt!): [String!]!
    # ... other fields
}
type C implements UInterface {
    someField(after: String, first: NonNegativeInt!): [String!]!
    # ... other fields
}

type UConnection {
    edges: [UEdge!]!
    pageInfo: PageInfo!
}

type UEdge {
    cursor: String!
    node: U!
}

type Query {
    u: UConnection!
}

Versions

macOS 14.2.1 (23C71) @eddeee888/gcg-typescript-resolver-files v0.7.2

eddeee888 commented 3 months ago

Hi @lensbart 👋 Apologies for the delay, I feel that there's a few things that could be unpacked here:

  1. Usually if there's already an interface and we only expect to return types implementing that interface, we could use that instead of going through a union e.g.
type UEdge {
  cursor: String!
  node: UInterface! # `UInterface` can be used instead of `U`. https://graphql.com/learn/interfaces-and-unions/#interface-as-a-return-type
}

However, I think your use case still works as your results would go through a the Union route, instead of the Interface route.

  1. Interfaces and Unions cannot be returned directly, so we can just declare AMapper, BMapper and CMapper.

For example, here's the mappers I'm using for your example:

// schema.mappers.ts
export type AMapper = {
  id: string;
  onlyA: string; // imagine this is used to resolve `A.someFields`
};
export type BMapper = {
  id: string;
  onlyB: string; // imagine this is used to resolve `B.someFields`
};
export type CMapper = {
  id: string;
  onlyC: string; // imagine this is used to resolve `C.someFields`
};

and let's use simple GraphQL query to test:

type Query {
  uInterface: Uinterface!
}

Then, in Query.uInterface, we can see that it'd expect the return object to be one of the 3 mappers like this video example:

https://github.com/eddeee888/graphql-code-generator-plugins/assets/33769523/168fc1f4-5942-42d9-91c1-a2ac2c2fab17

And in B object resolvers such as B.someFields, we can see that the parent has the BMapper type that we could use to implement the resolver logic:

Screenshot 2024-03-22 at 10 42 08 pm

Note: that instead of using __resolveType, we are using explicit __typename to tell GraphQL which type: A, B, or C to be called. So, the default __typename is required when returning Union or Interface objects.


This is the expected behaviour that I can observe at the current v0.7.2. However, maybe there's something I'm missing ? Could you help me understand what you expect to happen please? 🙂