Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

type: object and additionalProperties: type: array causing is not assignable to 'string' index type #1055

Open
1 task
joshunger opened this issue Mar 22, 2023 · 7 comments
Labels
bug Something isn't working openapi-ts Relevant to the openapi-typescript library

Comments

@joshunger
Copy link

Description

I'm seeing in our existing code we have a type object where we've defined additional properties as an array. When we run openapi-typescript the TypeScript produced doesn't compile. Should this work? Thanks for the help!

TS2411: Property 'totals' of type '{ count?: number; }' is not assignable to 'string' index type 'Record<string, never>[]'.
    10 |     B: Record<string, never>;
    11 |     Example: {
  > 12 |       totals?: {
       |       ^^^^^^
    13 |         count?: number;
    14 |       };
    15 |       [key: string]: (components['schemas']['A'] & components['schemas']['B'])[] | undefined;
Name Version
openapi-typescript 6.2.0
Node.js 14.17.6
OS + version macOS 13

Reproduction

schema.yaml

components:
  schemas:
    A:
      type: object
    B:
      type: object
    Example:
      type: object
      properties:
        totals:
          type: object
          properties:
            count:
              type: integer
      additionalProperties:
        type: array
        items:
          allOf:
            - $ref: '#/components/schemas/A'
            - $ref: '#/components/schemas/B'
npx openapi-typescript ./schema.yaml

Expected result

Creates valid TypeScript that compiles.

Checklist

@joshunger
Copy link
Author

Maybe this is just invalid?

@mitchell-merry
Copy link
Contributor

mitchell-merry commented Apr 7, 2023

This is a tricky one.

Let's take a simplified schema:

Example:
  type: object
  properties:
    totals:
      type: number
  additionalProperties:
    type: string

v6 generates:

Example: {
  totals?: number;
  [key: string]: string | undefined;
};

while v5 generates:

Example: {
  totals?: number;
} & { [key: string]: string };

Note the difference - an intersection. While the v5 generated types compile, they're not actually useful, since trying to assign anything to that type will fail:

examples/1055_v5.ts:16:7 - error TS2322: Type '{ totals: number; }' is not assignable to type '{ totals: number; } & { [key: string]: string; }'.
  Type '{ totals: number; }' is not assignable to type '{ [key: string]: string; }'.
    Property 'totals' is incompatible with index signature.
      Type 'number' is not assignable to type 'string'.

const myExample: components["schemas"]["Example"] = {
  totals: 4
}

Following discussion from microsoft/TypeScript#17867... there's a misunderstanding on what the index signature here actually means. If you were to index an object of this type with an arbitrary string, what would you expect? You would expect string, but if that string is totals, then you actually get number. See Ryan Cavanaugh's comment: microsoft/TypeScript#17867 (comment)

So, what we probably want to generate is actually:

Example: {
  totals?: number;
  [key: string]: string | undefined | number;
};

which works as on the tin. Index with an arbitrary string, and you get string, other than the case where the string happens to be totals, where it's number. So the type should be string | number.

Does that make sense? This is a genuine bug.

@drwpow drwpow added the bug Something isn't working label Apr 7, 2023
@joshunger
Copy link
Author

@mitchell-merry excellent explanation!

@drwpow drwpow added the openapi-ts Relevant to the openapi-typescript library label May 22, 2023
@tyronen9
Copy link

tyronen9 commented Jul 14, 2023

I'm experiencing this when trying to generate a wrapper for Creditsafe's API.

The issue is at line 21764 of their spec. Simplified, it's:

"ProblemDetails": {
  "type": "object",
  "properties": {
    "title": "string"
  },
  "additionalProperties": {
    "type": "object"
  }
}

The generator produces

ProblemDetails: {
  title?: string;
  [key: string]: Record<string, never> | undefined;
};

which is not valid TypeScript. But afaict this is valid OpenAPI.

Basically it's all or nothing. If you have additionalProperties, you can't have any other properties.

The issue is that key could clash at runtime with any of the other fields. We'd need to define an enum consisting of all the keys of that type, then allow key to be any string except that type. Not sure if this is possible.

@drwpow
Copy link
Contributor

drwpow commented Jul 14, 2023

The issue is that key could clash at runtime with any of the other fields. We'd need to define an enum consisting of all the keys of that type, then allow key to be any string except that type. Not sure if this is possible.

I agree this is probably the right fix. And it should be possible.

@mitchell-merry
Copy link
Contributor

I don't think it's currently possible to specify a type string except for a particular string. There is an issue open for negated types: microsoft/TypeScript#4196 that would let us do something like [key: string & not 'title'] for the index signature. Would be a good use case for that operand I suppose

@drwpow
Copy link
Contributor

drwpow commented Jul 14, 2023

Yeah I’m wondering if reverting to the old behavior of an intersection + a TS helper (or it sounds crazy, but maybe a union, depending on how it behaves in practice) would yield better results until microsoft/TypeScript#4196 resolves (which may not be anytime soon). I think it’s at least worth exploring some (non-breaking) TS shenanigans here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working openapi-ts Relevant to the openapi-typescript library
Projects
None yet
Development

No branches or pull requests

4 participants