lukeautry / tsoa

Build OpenAPI-compliant REST APIs using TypeScript and Node
MIT License
3.48k stars 498 forks source link

Extending an intersection of interfaces causes missing fields in the OpenAPI Spec #1067

Open daniimara opened 3 years ago

daniimara commented 3 years ago

For the intersection scenario below, the fields active and results are missing on the swagger.json generated:

interface IAssessment {
    results?: string[];
}

type TEntity = IAssessment & {
    active: boolean;
};

interface IPerson extends TEntity {
    firstName: string;
    lastName: string;
}

image

Sorting

Expected Behavior

The fields active and results should be on the swagger.json generated:

image

"IPerson": {
          "properties": {
              "results": {
                  "items": {
                      "type": "string"
                  },
                  "type": "array"
              },
              "active": {
                  "type": "boolean"
              },
              "firstName": {
                  "type": "string"
              },
              "lastName": {
                  "type": "string"
              }
          },
          "required": [
              "active",
              "firstName",
              "lastName"
          ],
          "type": "object",
          "additionalProperties": false
}

Current Behavior

The fields active and results are missing on the swagger.json generated:

"IPerson": {
        "properties": {
            "firstName": {
                "type": "string"
            },
            "lastName": {
                "type": "string"
            }
        },
        "required": [
            "firstName",
            "lastName"
        ],
        "type": "object",
        "additionalProperties": false
},

Possible Solution

I suggest change the function getModelInheritedProperties at cli/src/metadataGeneration/typeResolver.ts:

image

if (type.dataType === 'refObject') {
    properties = [...properties, ...type.properties];
  } else if (type.dataType === 'nestedObjectLiteral') {
    properties = [...properties, ...type.properties];
  **} else if (type.dataType === 'intersection') {
    for (const prop of type.types) {
        if (prop.dataType === 'refObject' || prop.dataType === 'nestedObjectLiteral') {
          properties = [...properties, ...prop.properties];
        }
    }**
}

Steps to Reproduce

Try to generate the spec to:

interface IAssessment {
    results?: string[];
}

type TEntity = IAssessment & {
    active: boolean;
};

interface IPerson extends TEntity {
    firstName: string;
    lastName: string;
}

Context (Environment)

Version of the library: 3.9.0 Version of NodeJS: v12.13.1

Detailed Description

I am proposing a change to support the intersection scenario described.

Breaking change?

No.

WoH commented 3 years ago

Seems like we should consider intersection and union scenarios. Your suggestion is good, we need to generalize it beyond the one use case from the initial bug report. If we encounter an intersection, we need to get properties from all n branches recursively. Similarly, in case of a union, we should get properties from all n branches and mark them as optional (?).

ionmoraru-toptal commented 1 year ago

I bump into the same issue, any updates on it?

I use Zod and it doesn't take required into consideration for validation

controller:

public async createUser(
    @Body() body: UserCreateRequest
  ): Promise<UserResponse> {

type definition:

export const UserCreate = UserBase.omit({ id: true })
  .required({
    email: true,
  })
  .and(UserSchema.pick({ password: true }))

export type UserCreateRequest = ReturnType<typeof UserCreate.parse>
blipk commented 4 weeks ago

This is very problematic - makes intersection types utterly useless in tsoa - can't update a type to make some properties optional (or required) - can't get proper schema validation or examples for anything using an intersection type.