openapistack / openapicmd

The CLI for all things OpenAPI and Swagger
https://openapistack.co
MIT License
55 stars 9 forks source link

Discriminated union generated as `null | null` when property is nullable #63

Open MalachiMackie opened 3 days ago

MalachiMackie commented 3 days ago

Hi, I encountered an issue when generating a discriminated union type with npx openapicmd typegen. When the discriminated union is assigned as a nullable property, the type is generated as null | null. but when assigned as a nullable: false property, it's generated fine.

I managed to get a minimal reproduction with the following command and apidoc:

command

npx -y openapicmd typegen api-doc.json > client.ts

api-doc.json

{
  "openapi": "3.0.3",
  "info": {
    "title": "budgeting-backend",
    "description": "",
    "license": {
      "name": ""
    },
    "version": "0.1.0"
  },
  "paths": {},
  "components": {
    "schemas": {
      "Budget": {
        "type": "object",
        "required": [
      "requiredTarget"
        ],
        "properties": {
          "nullableTarget": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BudgetTarget1"
              }
            ],
            "nullable": true
          },
      "requiredTarget": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BudgetTarget2"
          }
        ],
        "nullable": false
      }
        }
      },     
      "BudgetTarget1": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "target_amount",
              "type"
            ],
            "properties": {
              "target_amount": {
                "type": "number",
                "format": "float"
              },
              "type": {
                "type": "string",
                "enum": [
                  "OneTime"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "target_amount",
              "type"
            ],
            "properties": {
              "target_amount": {
                "type": "number",
                "format": "float"
              },
              "type": {
                "type": "string",
                "enum": [
                  "Repeating"
                ]
              }
            }
          }
        ],
        "discriminator": {
          "propertyName": "type"
        }
      },
      "BudgetTarget2": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "target_amount",
              "type"
            ],
            "properties": {
              "target_amount": {
                "type": "number",
                "format": "float"
              },
              "type": {
                "type": "string",
                "enum": [
                  "OneTime"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "target_amount",
              "type"
            ],
            "properties": {
              "target_amount": {
                "type": "number",
                "format": "float"
              },
              "type": {
                "type": "string",
                "enum": [
                  "Repeating"
                ]
              }
            }
          }
        ],
        "discriminator": {
          "propertyName": "type"
        }
      }
    }
  }
}

generated client.ts

import type {
  OpenAPIClient,
  Parameters,
  UnknownParamsObject,
  OperationResponse,
  AxiosRequestConfig,
} from 'openapi-client-axios';

declare namespace Components {
    namespace Schemas {
        export interface Budget {
            nullableTarget?: null | null;
            requiredTarget: {
                target_amount: number; // float
                type: "OneTime";
            } | {
                target_amount: number; // float
                type: "Repeating";
            };
        }
        export type BudgetTarget1 = null | null;
        export type BudgetTarget2 = {
            target_amount: number; // float
            type: "OneTime";
        } | {
            target_amount: number; // float
            type: "Repeating";
        };
    }
}

export interface OperationMethods {
}

export interface PathsDictionary {
}

export type Client = OpenAPIClient<OperationMethods, PathsDictionary>

export type Budget = Components.Schemas.Budget;
export type BudgetTarget1 = Components.Schemas.BudgetTarget1;
export type BudgetTarget2 = Components.Schemas.BudgetTarget2;

note that I had to copy BudgetTarget into two different schemas in order to demonstrate it generating correctly for requiredTarget. If they both reference the same schema, then both properties generate as null | null