hey-api / openapi-ts

✨ Turn your OpenAPI specification into a beautiful TypeScript client
https://heyapi.vercel.app
MIT License
768 stars 63 forks source link

Generic Types Not Resolving Correctly #765

Open chasewallis opened 2 weeks ago

chasewallis commented 2 weeks ago

Description

We've got a number of types with generics. It appears that only 1 (first or last) wins the generic game and is used as the type for all.

For example, we've got endpoints that return something like this: (C#) ItemModel<bool> and ItemModel<string>. We've also got very deeply nested generic types, but this is enough to see the issue. Those get generated correctly to swagger.json:

"schema": {
  "$ref": "#/components/schemas/CompanyName.Infrastructure.Model.ItemModel`1[System.String]"
}

and

"schema": {
  "$ref": "#/components/schemas/CompanyName.Infrastructure.Model.ItemModel`1[System.Boolean]"
}

And the relevant section in the components: { schemas: section:

    "schemas": {
      "CompanyName.Infrastructure.Model.ItemModel`1[System.Boolean]": {
        "type": "object",
        "properties": {
          "item": {
            "type": "boolean"
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "hasError": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false
      },
      "CompanyName.Infrastructure.Model.ItemModel`1[System.String]": {
        "type": "object",
        "properties": {
          "item": {
            "type": "string",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "hasError": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false
      }
    **}**

So that all looks fine, but when the hey-api tool is ran, I get something like this in the types.gen.ts:

export type GetApiLotFindLotByIdResponse = CompanyName_Infrastructure_Model_ItemModel_1;

export type GetApiLotTestManufacturingToolCompatibilityResponse = CompanyName_Infrastructure_Model_ItemModel_1;

export type $OpenApiTs = {
    '/api/Lot/FindLotById': {
        get: {
            req: GetApiLotFindLotByIdData;
            res: {
                /**
                 * Success
                 */
                200: CompanyName_Infrastructure_Model_ItemModel_1;
            };
        };
    };
    '/api/Lot/TestManufacturingToolCompatibility': {
        get: {
            res: {
                /**
                 * Success
                 */
                200: CompanyName_Infrastructure_Model_ItemModel_1;
            };
        };
    };
};

So both responses are now returning the exact same type and completely ignoring the generic-ness of the types.

There is a very good chance I am using the tool wrong, have something misconfigured, etc. But I haven't found anything in the documentation that might help. I'm running the above with the most simple args to the tool, just in and out params.

Included is the swagger file I used to reproduce the issue.

test2.json

Reproducible example or configuration

openapi-ts -i provided.json -o genericsNotWorking

OpenAPI specification (optional)

{
  "openapi": "3.0.1",
  "info": {
    "title": "Mfg Api",
    "version": "V1"
  },
  "paths": {
    "/api/Lot/FindLotById": {
      "get": {
        "tags": [
          "Lot"
        ],
        "parameters": [
          {
            "name": "lotid",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyName.Infrastructure.Model.ItemModel`1[System.String]"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyName.Infrastructure.Model.ItemModel`1[System.String]"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyName.Infrastructure.Model.ItemModel`1[System.String]"
                }
              }
            }
          }
        }
      }
    },
    "/api/Lot/TestManufacturingToolCompatibility": {
      "get": {
        "tags": [
          "Lot"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyName.Infrastructure.Model.ItemModel`1[System.Boolean]"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyName.Infrastructure.Model.ItemModel`1[System.Boolean]"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyName.Infrastructure.Model.ItemModel`1[System.Boolean]"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CompanyName.Infrastructure.Model.ItemModel`1[System.Boolean]": {
        "type": "object",
        "properties": {
          "item": {
            "type": "boolean"
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "hasError": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false
      },
      "CompanyName.Infrastructure.Model.ItemModel`1[System.String]": {
        "type": "object",
        "properties": {
          "item": {
            "type": "string",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "hasError": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false
      }
    }
  }
}

System information (optional)

No response

mrlubos commented 2 weeks ago

Thank you for reporting @chasewallis

mrlubos commented 1 week ago

@chasewallis what would be the expected result for you?

mrlubos commented 3 days ago

In unrelated news, I found the matcher responsible for this issue, but waiting for your response before prioritising this

chasewallis commented 3 days ago

Sorry, been in vacation and still am. The only thing I really care about is that the generic types are represented as different types and not the same. So I guess the expected result in the example given would be that the 2 endpoints return types that truly represent the generic type.

mrlubos commented 3 days ago

Thank you, enjoy your vacation!