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

Types derived for oneOf construct result in TS2502 error

Open swachter opened this issue 1 year ago • 9 comments

Description

Processing the OpenAPI spec of the Mongo Atlas Admin API I found that the oneOf construct yields to types that have a cyclic reference that result in TypeScript error TS2502. E.g.:

src/openapi/mongo-atlas-admin-v2.mts:10468:5 - error TS2502: 'IngestionSource' is referenced directly or indirectly in its own type annotation.

10468     IngestionSource: ({

This is the OpenAPI spec of IngestionSource:

      "IngestionSource": {
        "type": "object",
        "description": "Ingestion Source of a Data Lake Pipeline.",
        "discriminator": {
          "mapping": {
            "ON_DEMAND_CPS": "#/components/schemas/OnDemandCpsSnapshotSource",
            "PERIODIC_CPS": "#/components/schemas/PeriodicCpsSnapshotSource"
          },
          "propertyName": "type"
        },
        "oneOf": [
          {
            "$ref": "#/components/schemas/OnDemandCpsSnapshotSource"
          },
          {
            "$ref": "#/components/schemas/PeriodicCpsSnapshotSource"
          }
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Type of ingestion source of this Data Lake Pipeline.",
            "enum": ["PERIODIC_CPS", "ON_DEMAND_CPS"]
          }
        },
        "title": "Ingestion Source"
      },

The reason is that in the member types the discriminator property is explicitly omitted, thereby creating the cyclic refernce:

    IngestionSource: ({
      /**
       * @description Type of ingestion source of this Data Lake Pipeline.
       * @enum {string}
       */
      type?: "PERIODIC_CPS" | "ON_DEMAND_CPS";
    }) & (components["schemas"]["OnDemandCpsSnapshotSource"] | components["schemas"]["PeriodicCpsSnapshotSource"]);

...

    OnDemandCpsSnapshotSource: {
      type: "ON_DEMAND_CPS";
    } & Omit<components["schemas"]["IngestionSource"], "type"> & {
      /** @description Human-readable name that identifies the cluster. */
      clusterName?: string;
      /** @description Human-readable name that identifies the collection. */
      collectionName?: string;
      /** @description Human-readable name that identifies the database. */
      databaseName?: string;
      /**
       * @description Unique 24-hexadecimal character string that identifies the project.
       * @example 32b6e34b3d91647abb20e7b8
       */
      groupId?: string;
    };

If the Omit<components["schemas"]["IngestionSource"], "type"> part is omitted ( ;-) ), then the cyclic reference is cut. TypeScript is still able to handle the resulting discriminated union type.

Note: If such invalid types are imported from d.ts files then tsc just falls back to any, loosing all type safetyness.

Name Version
openapi-typescript 6.7.5

Reproduction

Run openapi-typescript on the mentioned OpenAPI spec and compile it by tsc.

  • [x] The issues of the OpenAPI schema reported by Redocly validator (npx @redocly/cli@latest lint) are unrelated
  • [ ] I’m willing to open a PR (see CONTRIBUTING.md)

swachter avatar May 17 '24 15:05 swachter

Please try it with version 7 and let us know if the problem still arises. The oneOf discriminator support was completely rewritten and does not use Omit anymore.

mzronek avatar May 19 '24 20:05 mzronek

I just checked with 7.0.0-rc.0. There are still cyclic references. The OnDemandCpsSnapshotSource type still includes the Omit<components["schemas"]["IngestionSource"], "type"> part.

swachter avatar May 27 '24 07:05 swachter

I encountered the same issue here. I tested it locally with 7.0.0-rc.0 using the following specifications.

{
    "openapi": "3.0.1",
    "components": {
        "schemas": {
            "Animal": {
                "required": [
                    "type"
                ],
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string"
                    }
                },
                "description": "Animal",
                "discriminator": {
                    "propertyName": "type",
                    "mapping": {
                        "DOG": "#/components/schemas/Dog",
                        "CAT": "#/components/schemas/Cat"
                    }
                },
                "oneOf": [
                    {
                        "$ref": "#/components/schemas/Dog"
                    },
                    {
                        "$ref": "#/components/schemas/Cat"
                    }
                ]
            },
            "Cat": {
                "type": "object",
                "allOf": [
                    {
                        "$ref": "#/components/schemas/Animal"
                    },
                    {
                        "type": "object",
                        "properties": {
                            "age": {
                                "type": "integer",
                                "format": "int32"
                            }
                        }
                    }
                ]
            },
            "Dog": {
                "type": "object",
                "allOf": [
                    {
                        "$ref": "#/components/schemas/Animal"
                    },
                    {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            }
                        }
                    }
                ]
            }
        }
    }
}

I believe my issue might be related to this, but I'm uncertain. It would be helpful to confirm if this is indeed the case.

EggOxygen avatar May 27 '24 08:05 EggOxygen

You need to remove the oneOf from Animal. It is inferred by the inherited discriminator.

Please check the unit tests for supported discriminator usages: https://github.com/drwpow/openapi-typescript/blob/main/packages/openapi-typescript/test/discriminators.test.ts

mzronek avatar May 29 '24 13:05 mzronek

You need to remove the oneOf from Animal. It is inferred by the inherited discriminator.

Thank you for your reply. I have already looked for the test when I first encountered this issue. It initially started at https://github.com/drwpow/openapi-typescript/pull/1574.


I know we can solve this issue by remove the oneOf. But I checked the sample that @swachter provided, which is also similar to my case. Isn't this also a valid way to do this? Even if it's inferred by the inherited discriminator.

      "IngestionSource": {
        "type": "object",
        "description": "Ingestion Source of a Data Lake Pipeline.",
        "discriminator": {
          "mapping": {
            "ON_DEMAND_CPS": "#/components/schemas/OnDemandCpsSnapshotSource",
            "PERIODIC_CPS": "#/components/schemas/PeriodicCpsSnapshotSource"
          },
          "propertyName": "type"
        },
        "oneOf": [
          {
            "$ref": "#/components/schemas/OnDemandCpsSnapshotSource"
          },
          {
            "$ref": "#/components/schemas/PeriodicCpsSnapshotSource"
          }
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Type of ingestion source of this Data Lake Pipeline.",
            "enum": [
              "PERIODIC_CPS",
              "ON_DEMAND_CPS"
            ]
          }
        },
        "title": "Ingestion Source"
      },
      "OnDemandCpsSnapshotSource": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/IngestionSource"
          },
          {
            "type": "object",
            "properties": {
              "clusterName": {
                "type": "string",
                "description": "Human-readable name that identifies the cluster."
              },
              "collectionName": {
                "type": "string",
                "description": "Human-readable name that identifies the collection."
              },
              "databaseName": {
                "type": "string",
                "description": "Human-readable name that identifies the database."
              },
              "groupId": {
                "type": "string",
                "description": "Unique 24-hexadecimal character string that identifies the project.",
                "example": "32b6e34b3d91647abb20e7b8",
                "maxLength": 24,
                "minLength": 24,
                "pattern": "^([a-f0-9]{24})$",
                "readOnly": true
              }
            }
          }
        ],
        "description": "On-Demand Cloud Provider Snapshots as Source for a Data Lake Pipeline.",
        "title": "On-Demand Cloud Provider Snapshot Source"
      },

EggOxygen avatar May 31 '24 07:05 EggOxygen

The JSON Schema Project and the OpenAPI Specification do not restrict the usage of circular references. They pass the problem on to the tooling to be solved (a linter or a validator for example). Details here.

If I throw the Animal schema against an OpenAPI validator I am getting a recursion limit error on the first oneOf reference, that is resolved. So this case is an invalid schema. In very rare cases openapi-typescript handles invalid schemas if the case is for example very common. In my opinion this is not true here.

mzronek avatar Jun 02 '24 08:06 mzronek

Many thanks for the clarification, @mzronek .

I wonder if the code generator could do better in certain situations. For example manually removing the Omit part from the created code did work for me. Maybe such situations could be detected.

swachter avatar Jun 02 '24 11:06 swachter

This issue is stale because it has been open for 90 days with no activity. If there is no activity in the next 7 days, the issue will be closed.

github-actions[bot] avatar Sep 01 '24 02:09 github-actions[bot]

This issue was closed because it has been inactive for 7 days since being marked as stale. Please open a new issue if you believe you are encountering a related problem.

github-actions[bot] avatar Sep 09 '24 02:09 github-actions[bot]