openapi-typescript icon indicating copy to clipboard operation
openapi-typescript copied to clipboard

Incorrect type generation when using a `$ref` with `discriminator` inside `allOf`

Open EnergyCube opened this issue 1 year ago • 7 comments

Preamble

Thank you for your work on this project, keep up the good work!

Description

A wrong type is generated when using a $ref from schemas that contains a discriminator inside an allOf. An invalid Omit occurs and removes the discriminator to force a "json" value string.

Name Version
openapi-typescript 6.7.1
Node.js 18.18.2
OS + version Windows 11

Reproduction

This following OpenAPI json will produce the following invalid type:

content: {
  "application/json": {
    my_type: "json";
  } & Omit<components["schemas"]["allData"], "my_type">;
};
{
  "openapi": "3.0.0",
  "info": {
    "version": "1.0.0",
    "title": "openapi-typescript bug reproduction",
    "license": {
      "name": "DWTFYW",
      "url": "https://en.wikipedia.org/wiki/WTFPL"
    }
  },
  "servers": [
    {
      "url": "n/a"
    }
  ],
  "paths": {
    "/performRequest": {
      "post": {
        "security": [],
        "operationId": "performRequest",
        "summary": "POST /performRequest",
        "parameters": [],
        "responses": {
          "200": {
            "description": "POST /performRequest",
            "content": {
              "application/json": {
                "schema": {
                  "title": "performRequest Response",
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/allData"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "allData": {
        "type": "object",
        "discriminator": {
          "propertyName": "my_type"
        },
        "required": ["my_type"],
        "oneOf": [
          { "$ref": "#/components/schemas/data1" },
          { "$ref": "#/components/schemas/data2" }
        ]
      },
      "data1": {
        "type": "object",
        "properties": {
          "my_type": {
            "type": "string",
            "enum": ["data_type_ex_1"]
          }
        },
        "required": ["my_type"]
      },
      "data2": {
        "type": "object",
        "properties": {
          "my_type": {
            "type": "string",
            "enum": ["data_type_ex_2"]
          }
        },
        "required": ["my_type"]
      }
    }
  }
}

Expected result

content: {
  "application/json": components["schemas"]["allData"];
};

Remark

Taking the content of the $ref and putting it in the allOf works correctly, but the problem seems to be specific to the use of a $ref in a allOf.

The bug seems to occur from 6.6.1.

Checklist

EnergyCube avatar Nov 28 '23 23:11 EnergyCube

I believe we are seeing this same issue (or a similar issue), but with the $ref appearing inside an anyOf rather than allOf. I can try to provide a minimal repro if that is helpful.

P.S.: +1 thank you for this library! It's super helpful for our application.

jasonm avatar Jan 20 '24 20:01 jasonm

Again, great job with this package! It's the perfect solution to our use case!

And yes, can confirm this bug is occurring in 7.0.0-next.8 too.

It's incorrectly omitting the property used as a discriminator.

OpenApi YML:

ObjectOne:
      type: object
      additionalProperties: false
      oneOf:
        - $ref: '#/components/schemas/Bar'
        - $ref: '#/components/schemas/Baz'
      discriminator:
        propertyName: foo
        mapping:
          bar: '#/components/schemas/Bar'
          baz: '#/components/schemas/Baz'

Actual TS:

AnotherObject: {
      arrayOfObjectOnes?: (Omit<components["schemas"]["ObjectOne"], "foo"> & Record<string, never>)[];
}

Expected TS:

AnotherObject: {
      arrayOfObjectOnes?: components["schemas"]["ObjectOne"][];
}

gamell avatar Mar 20 '24 23:03 gamell

I might be completely out of my depth here, but I think the bug might be coming from here?

https://github.com/drwpow/openapi-typescript/blob/92f4b96f655538adf366a7e380f8a93b536f65fa/packages/openapi-typescript/src/transform/schema-object.ts#L190-L194

gamell avatar Mar 21 '24 17:03 gamell

I am not too familiar with discriminators, I wanted to ask if the problem I am seeing has the same root cause of this issue? Thanks!

The generated chatCompletionRequestMessage type (with 7.0.0-next.8) from this spec is missing the discriminator mappings?

chatCompletionRequestMessage: {
    role: components["schemas"]["chatCompletionRequestMessageRole"];
    // no mappings here?
};
chatCompletionRequestMessageRole: "system" | "user" | "assistant" | "tool" | "function";

The mapping types are generated correctly though, for now I use this workaround:

type chatCompletionRequestMessage = (
  | components['schemas']['chatCompletionRequestMessageSystem']
  | components['schemas']['chatCompletionRequestMessageUser']
  | components['schemas']['chatCompletionRequestMessageAssistant']
  | components['schemas']['chatCompletionRequestMessageTool']
  | components['schemas']['chatCompletionRequestMessageFunction']
);

tvanier avatar Mar 22 '24 11:03 tvanier

I am not too familiar with discriminators, I wanted to ask if the problem I am seeing has the same root cause of this issue? Thanks!

I don't think this is the same issue. If it were, you'd be seeing an Omit<...> in the generated code

gamell avatar Mar 22 '24 23:03 gamell

I believe i'm still seeing this issue

CalebJamesStevens avatar Apr 29 '24 17:04 CalebJamesStevens

Any updates on this? I just encountered it

ThomasConrad avatar Jul 29 '24 12:07 ThomasConrad