Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request] Better discriminator handling in union types #5201

Open
tsirlucas opened this issue Mar 5, 2023 · 1 comment
Open

[Feature request] Better discriminator handling in union types #5201

tsirlucas opened this issue Mar 5, 2023 · 1 comment

Comments

@tsirlucas
Copy link
Contributor

tsirlucas commented Mar 5, 2023

Is your feature request related to a problem? Please describe.

Typescript codegen does not apply open api discriminator as string constant in related objects.

For example, in the following open api file

openapi: 3.0.0
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
paths:
  /pets/{id}:
    get:
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        200:
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'

components:
  schemas:
    Pet:
      oneOf:
        - $ref: '#/components/schemas/Cat'
        - $ref: '#/components/schemas/Dog'
      discriminator:
        propertyName: petType
        mapping:
          Dog: '#/components/schemas/Dog'
          Cat: '#/components/schemas/Cat'
    Cat:
      type: object
      properties:
        petType:
          type: string
        cat_exclusive:
          type: string
    Dog:
      type: object
      properties:
        petType:
          type: string
        dog_exclusive:
          type: string

Output is

schema {
  query: Query
}

directive @oneOf on OBJECT | INTERFACE

directive @discriminator(field: String) on INTERFACE | UNION

directive @globalOptions(sourceName: String, endpoint: String, operationHeaders: ObjMap, queryStringOptions: ObjMap, queryParams: ObjMap) on OBJECT

directive @httpOperation(path: String, operationSpecificHeaders: ObjMap, httpMethod: HTTPMethod, isBinary: Boolean, requestBaseBody: ObjMap, queryParamArgMap: ObjMap, queryStringOptionsByParam: ObjMap) on FIELD_DEFINITION

type Query @globalOptions(sourceName: "Pet") {
  pets_by_id(id: String!): Pet @httpOperation(path: "/pets/{args.id}", operationSpecificHeaders: "{\\"accept\\":\\"application/json\\"}", httpMethod: GET)
}

union Pet @discriminator(field: "petType") = Cat | Dog

type Cat {
  petType: String
  cat_exclusive: String
}

type Dog {
  petType: String
  dog_exclusive: String
}

scalar ObjMap

enum HTTPMethod {
  GET
  HEAD
  POST
  PUT
  DELETE
  CONNECT
  OPTIONS
  TRACE
  PATCH
}

Which is correct, but then, when generating types, the discriminator is completely ignored

export type Pet = Cat | Dog;

export type Cat = {
  petType?: Maybe<Scalars['String']>;
  cat_exclusive?: Maybe<Scalars['String']>;
};

export type Dog = {
  petType?: Maybe<Scalars['String']>;
  dog_exclusive?: Maybe<Scalars['String']>;
};

This way we lost the ability to narrow Pet down to Cat or Dog using its petType property.

One alternative that fixes the issue is to declare the properties as constants like the following:

openapi: 3.0.0
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
paths:
  /pets/{id}:
    get:
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        200:
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'

components:
  schemas:
    Pet:
      oneOf:
        - $ref: '#/components/schemas/Cat'
        - $ref: '#/components/schemas/Dog'
      discriminator:
        propertyName: petType
        mapping:
          Dog: '#/components/schemas/Dog'
          Cat: '#/components/schemas/Cat'
    Cat:
      type: object
      properties:
        petType:
          type: string
          enum: ['Cat']
        cat_exclusive:
          type: string
    Dog:
      type: object
      properties:
        petType:
          type: string
          enum: ['Dog']
        dog_exclusive:
          type: string

Then, we get something like the following in schema

type Cat {
  petType: Cat_const
  cat_exclusive: String
}

enum Cat_const @typescript(type: "\"Cat\"") @example(value: "\"Cat\"") {
  Cat @enum(value: "\"Cat\"")
}

And types are correct

export type Pet = Cat | Dog;

export type Cat = {
  petType?: Maybe<Cat_const>;
  cat_exclusive?: Maybe<Scalars['String']>;
};

export type Cat_const =
  | 'Cat';

The issue with that approach is that we will now have conflicting constants in our unions in graphql.

Describe the solution you'd like

I would like one of the following solutions

  • Discriminated items should be tagged with some kind of directive in graphql schema. They should still be a String, but TS codegen should use the directive to resolve the types correctly.
  • A flag to tell the schema generator to threat enums as strings (not as clean as first one, but based on the current workaround we use)

Describe alternatives you've considered

We currently use the enum solution, but then we do some post-processing in the schemas and replace all constants with strings so we dont face the problem with conflicting types. Thats not a nice solution tho as we are manipulating generated code and if we want to use constants at some point, we wont be able to.

This was referenced Apr 30, 2024
This was referenced May 7, 2024
@vanbujm
Copy link

vanbujm commented Oct 9, 2024

I'm facing the same problem. Happy to submit a fix if someone could point me to where codgen happens. 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants