openapi-generator: [typescript] allOf with multiple values is not handled properly

Description

If a schema is defined with the allOf operator with multiple values, the generated model is an interface that extends only the first value.

openapi-generator version

3.2.2

OpenAPI declaration file content or url
openapi: 3.0.0
info:
  title: TestApi
  version: 1.0.0
paths:
  /test:
    get:
      summary: Test
      operationId: testApi
      responses:
        "200":
          description: Ok
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelC"
components:
  schemas:
    ModelA:
      properties:
        foo:
          type: string
    ModelB:
      properties:
        bar:
          type: string
    ModelC:
      allOf:
        - $ref: "#/components/schemas/ModelA"
        - $ref: "#/components/schemas/ModelB"
        - type: object
          properties:
            baz:
              type: string
Command line used for generation

docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli:v3.2.2 generate -i /local/swagger.yaml -g typescript-angular -o /local/ts-angular -DngVersion=6.0 docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli:v3.2.2 generate -i /local/swagger.yaml -g typescript-fetch -o /local/ts-fetch

Suggest a fix/enhancement

The model generated for ModelC is the following interface:

export interface ModelC extends ModelA { 
    baz?: string;
}

When it should be:

export interface ModelC extends ModelA, ModelB { 
    baz?: string;
}

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 6
  • Comments: 37 (31 by maintainers)

Most upvoted comments

Is this really fixed? Does not seem to work for typescript, typescript-fetch and typescript-axios generators. Would be great to have some feedback on this.

Another option for Typescript is the use of intersection types.

openapi: 3.0.0
info:
  title: TestApi
  version: 1.0.0
paths:
  /test:
    get:
      summary: Test
      operationId: testApi
      responses:
        "200":
          description: Ok
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelC"
components:
  schemas:
    ModelA:
      properties:
        foo:
          type: string
    ModelB:
      properties:
        bar:
          type: string
    ModelC:
      allOf:
        - $ref: "#/components/schemas/ModelA"
        - $ref: "#/components/schemas/ModelB"
        - type: object
          properties:
            baz:
              type: string
type ModelC = ModelA & ModelB & {
  baz?: string
}

Look like this was the breaking change: https://github.com/OpenAPITools/openapi-generator/pull/5526/files#diff-57d7532cf464a8d7c24aab4b22ceb993R1138

Current effect is:

  • for model (Child) that is composed as allOf on Parent and Model_AllOf, this if condition ensures Parent is treated as a parent in inheritance, even though it doesn’t have a discriminator.

However, getAllParentsName() was not updated to follow the same logic, so parent and allParents are out of sync now (later should always be a superset of former). And typescript generators rely on allParents. I’ll make a PR to fix this

I’ve just run into this same issue with the Spring generator, except in that case it’s generating classes so multiple inheritance wouldn’t work.

@wing328 no problem, latest stable is working OK for me

@amakhrov Thank you for fast response. Can you please provide example that is working for you ?

For me it does not work

    Node:
      type: object
      description: representation of a node
      properties:
        id:
          type: string
        type:
          type: string
        service:
          type: string
          enum:
            - library
            - information
            - events
            - newsgroups
            - directory
        name:
          type: string
        title:
          $ref: '#/components/schemas/i18nProperty'
        description:
          $ref: '#/components/schemas/i18nProperty'
        properties:
          type: object
          additionalProperties:
            type: string
        permissions:
          type: object
          additionalProperties:
            type: string
          description: >
            Representation of the permissions given to the user making the call.

            To know what are the permissions for the specific node, the
            permission

            definition may vary depending on its type or service.
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/Attachment'
        parentId:
          type: string
        notifications:
          type: string
        favourite:
          type: boolean
          description: |
            true if the current user faved the node
        hasSubFolders:
          type: boolean
    ArchiveNode:
      allOf:
        - $ref: '#/components/schemas/Node'
        - type: object
          properties:
            deletedBy:
              type: string
            deletedDate:
              type: string
              format: date

Archive node does not include properties of node !?

import { Node } from './node';
import { Attachment } from './attachment';
import { ArchiveNodeAllOf } from './archiveNodeAllOf';

export interface ArchiveNode {
  deletedBy?: string;
  deletedDate?: string;
}
export namespace ArchiveNode {} 

So I think it is broken. It was working before but not anymore there is some regression there.

@topce thanks for bringing this up. Indeed support of allOf across typescript generators is inconsistent and incomplete atm.

Maybe to test changes with one yaml file that cover all features of open api spec

Yep, that’s something that I still plan to do (filed in this issue) - decided to postpone till typescript examples cleanup by @macjohnny (which is planned to be done soon after 4.3.0 release) to avoid redundant work.

allOf without any discriminator: all properties from referenced schemas are cloned into the target interface.

It depends on a generator. I just tested typescript-axios and typescript-angular (using a version of 2_0/petstore-with-fake-endpoints-models-for-testing.yaml where I removed discriminator) - and they seem to emit valid model with all fields included.

Hi @macjohnny @wing328 @amakhrov

It looks like for same time this is broken

before

import { Node } from './node';
import { Attachment } from './attachment';
import { ArchiveNodeAllOf } from './archiveNodeAllOf';

export interface ArchiveNode {
  id?: string;
  type?: string;
  service?: ArchiveNode.ServiceEnum;
  name?: string;
  /**
   * The object that is used to compile all the translations of a node into a JSON object. It is basically composed of a map with a key languaguage code and its value
   */
  title?: { [key: string]: string };
  /**
   * The object that is used to compile all the translations of a node into a JSON object. It is basically composed of a map with a key languaguage code and its value
   */
  description?: { [key: string]: string };
  properties?: { [key: string]: string };
  /**
   * Representation of the permissions given to the user making the call. To know what are the permissions for the specific node, the permission definition may vary depending on its type or service.
   */
  permissions?: { [key: string]: string };
  attachments?: Array<Attachment>;
  parentId?: string;
  notifications?: string;
  /**
   * true if the current user faved the node
   */
  favourite?: boolean;
  hasSubFolders?: boolean;
  deletedBy?: string;
  deletedDate?: string;
}
export namespace ArchiveNode {
  export type ServiceEnum =
    | 'library'
    | 'information'
    | 'events'
    | 'newsgroups'
    | 'directory';
  export const ServiceEnum = {
    Library: 'library' as ServiceEnum,
    Information: 'information' as ServiceEnum,
    Events: 'events' as ServiceEnum,
    Newsgroups: 'newsgroups' as ServiceEnum,
    Directory: 'directory' as ServiceEnum
  };
}

that was relatively OK if we ignore not needed imports now

import { Node } from './node';
import { Attachment } from './attachment';
import { ArchiveNodeAllOf } from './archiveNodeAllOf';

export interface ArchiveNode {
  deletedBy?: string;
  deletedDate?: string;
}
export namespace ArchiveNode {} 

All generated from same model

    ArchiveNode:
      allOf:
        - $ref: '#/components/schemas/Node'
        - type: object
          properties:
            deletedBy:
              type: string
            deletedDate:
              type: string
              format: date

So little bit annoying same/similar error happen after few years in master Maybe to test changes with one yaml file that cover all features of open api spec

in attempt to minimize stupid regerssions
so last comment of @amakhrov is not correct any more because it look like generator is broken

  • allOf without any discriminator: all properties from referenced schemas are cloned into the target interface.

From the spec: While composition offers model extensibility, it does not imply a hierarchy between the models.

Extending an interface implies a parent-child relationship in the type hierarchy. This is therefore incorrect in the context of the spec. I think that intersection types would be appropriate to use in this case.

hi @wing328 I checked against master from today and it is even worst generated code

import { ModelA } from './modelA';
import { ModelB } from './modelB';


export interface ModelC { 
    baz?: string;
}

and it should be

import { ModelA } from './modelA';
import { ModelB } from './modelB';


export interface ModelC extends ModelA, ModelB { 
    baz?: string;
}

so it look like it is broken now Few days ago it was working (at least for one interface inheritance) so it is some recent PR that broke it

Hi folks, I’ve filed https://github.com/OpenAPITools/openapi-generator/pull/1360 with better inheritance/composition support. Please give it a try and let us know if you’ve any feedback for us.