OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.3k stars 6.44k forks source link

[BUG] [typescript-fetch] openapi v2 `discriminator` with `allOf` not generating desired type #10674

Open Liooo opened 2 years ago

Liooo commented 2 years ago

Bug Report Checklist

Description

using openapi v2 discriminator with allOfdoesn't generate desired model types.

https://swagger.io/specification/v2/

openapi-generator version

v5.2.1 (same in the current latest master)

OpenAPI declaration file content or url


swagger: "2.0"
info:
  description: bug test
  version: "1.0.0"
  title: bug test
schemes:
  - "https"
  - "http"
produces:
  - application/json
consumes:
  - application/json

paths:
  /values:
    get:
      tags:
        - "value"
      operationId: "getValues"
      responses:
        "200":
          description: ok
          schema:
            type: array
            items:
              $ref: "#/definitions/Value"
definitions:
  Value:
    type: object
    discriminator: valueType
    properties:
      name:
        type: string
      valueType:
        type: string
    required:
    - name
    - valueType
  Datetime:
    description: A representation of a datetime
    allOf:
    - $ref: '#/definitions/Value'
    - type: object
      properties:
        value:
          type: string
          format: 'date-time'
        datetimy:
          type: boolean
      required:
        - value
  Int:
    description: A representation of a int
    allOf:
    - $ref: '#/definitions/Value'
    - type: object
      properties:
        value:
          type: integer
        inty:
          type: boolean
      required:
        - value

Generation Details

openapi-generator generate \
   -g typescript-fetch
   -p typescriptThreePlus=true,supportsES6=true
   -i swagger.yml
   -o ./out

(adding - p legacyDiscriminatorBehavior=false doesn't really change anything)

Steps to reproduce

run the command above

expected behavior

api response has the field .value with the intended type

const api = new ValueApi()
const values = api.getValues()

if (values[0].valueType === 'Int') {
    let intValue number
    intValue = values[0].value  // <= works without error, and preferably has number type after `if (values[0].valueType === 'Int') `
}

actual behavior

.value field doesn' exist

...
intValue = values[0].value // =>  Property 'value' does not exist on type 'Value'.

actual output

// ----- apis/ValueApi.ts
export class ValueApi extends runtime.BaseAPI {
    // ...
    async getValuesRaw(): Promise<runtime.ApiResponse<Array<Value>>> {
        const queryParameters: any = {};

        const headerParameters: runtime.HTTPHeaders = {};

        const response = await this.request({
            path: `/values`,
            method: 'GET',
            headers: headerParameters,
            query: queryParameters,
        });

        return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(ValueFromJSON));
    }

// ----- models/Value.ts
export interface Value { 
    /**
     * 
     * @type {string}
     * @memberof Value
     */
    name: string;
    /**
     * 
     * @type {string}
     * @memberof Value
     */
    valueType: string;
}

export function ValueFromJSON(json: any): Value {
    return ValueFromJSONTyped(json, false);
}

export function ValueFromJSONTyped(json: any, ignoreDiscriminator: boolean): Value {
    if ((json === undefined) || (json === null)) {
        return json;
    }
    if (!ignoreDiscriminator) {
        if (json['valueType'] === 'Datetime') {
            return DatetimeFromJSONTyped(json, true);
        }
        if (json['valueType'] === 'Int') {
            return IntFromJSONTyped(json, true);
        }
    }
    return {

        'name': json['name'],
        'valueType': !exists(json, 'valueType') ? undefined : json['valueType'],
    };
}

// ----- models/Datetime.ts
export interface Datetime extends Value {
    /**
     * 
     * @type {Date}
     * @memberof Datetime
     */
    value: Date;
    /**
     * 
     * @type {boolean}
     * @memberof Datetime
     */
    datetimy?: boolean;
}

// ----- models/Int.ts
export interface Int extends Value {
    /**
     * 
     * @type {number}
     * @memberof Int
     */
    value: number;
    /**
     * 
     * @type {boolean}
     * @memberof Int
     */
    inty?: boolean;
}

Related issues/PRs

probably none

Suggest a fix

use something like discriminated union in tyepscript

Proof of concept:

Playground link

interface int {
    valueType: "Int"    
    value: number
}

interface datetime {
    valueType: "Datetime"
    value: Date
}

type value = int | datetime

function getValue(): value[] {
    return [
        {
          value: 1,
          valueType: 'Int',
        },
        {
          value: new Date(),
          valueType: 'Datetime',
        },
    ]        
}

getValue().map(v => {
    if (v.valueType === 'Int') {
        let intv: number
        intv = v.value
    } else if (v.valueType === 'Datetime') {
        let datev: Date
        datev = v.value
    }
})
pwespi commented 2 years ago

The typescript-angular generator as the config option taggedUnions, which sound like what you're describing.

ddeath commented 1 year ago

I see same error for typescript-axios with OA3