Skip to content

Type discrimination between data and error doesn't work #1609

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

Closed
1 task done
illright opened this issue Apr 7, 2024 · 3 comments · Fixed by #1610
Closed
1 task done

Type discrimination between data and error doesn't work #1609

illright opened this issue Apr 7, 2024 · 3 comments · Fixed by #1610
Labels
bug Something isn't working good first issue Straightforward problem, solvable for first-time contributors without deep knowledge of the project openapi-fetch Relevant to the openapi-fetch library PRs welcome PRs are welcome to solve this issue!

Comments

@illright
Copy link
Contributor

illright commented Apr 7, 2024

Description

A simple if-statement on dataRes.data fails to correctly discriminate data and error, which results in the error type being never:

const dataRes = await GET('/');

if (dataRes.data) {
  expectTypeOf(dataRes.data).toEqualTypeOf({
    objects: [{ id: '', name: '' }],
  });
} else {
  expectTypeOf(dataRes.data).toBeUndefined();
  expectTypeOf(dataRes.error).toEqualTypeOf({ errors: { body: [''] } });
}
The OpenAPI spec used for this example
openapi: 3.0.1
info:
  title: Test API
  version: 1.0.0
  
paths:
  /:
    get:
      description: List objects
      operationId: GetObjects
      responses:
        '200':
          $ref: '#/components/responses/ObjectsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/GenericError'

components:
  schemas:
    Object:
      required:
        - id
        - name
      type: object
      properties:
        id:
          type: string
        name:
          type: string
    GenericErrorModel:
      required:
        - errors
      type: object
      properties:
        errors:
          required:
            - body
          type: object
          properties:
            body:
              type: array
              items:
                type: string
  responses:
    ObjectsResponse:
      description: Objects
      content:
        application/json:
          schema:
            required:
              - objects
            type: object
            properties:
              objects:
                type: array
                items:
                  $ref: '#/components/schemas/Object'
    Unauthorized:
      description: Unauthorized
      content: {}
    GenericError:
      description: Unexpected error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GenericErrorModel'

Reproduction

https://stackblitz.com/edit/vitejs-vite-vnadlg?file=README.md&view=editor

Expected result

It should allow checking dataRes.data or dataRes.error and choosing either the success type or the error type

Checklist

@illright illright added bug Something isn't working openapi-fetch Relevant to the openapi-fetch library labels Apr 7, 2024
@drwpow drwpow added PRs welcome PRs are welcome to solve this issue! good first issue Straightforward problem, solvable for first-time contributors without deep knowledge of the project labels Apr 7, 2024
@illright
Copy link
Contributor Author

illright commented Apr 7, 2024

I investigated this issue further, turns out that the problem is in the FilterKeys utility type and its filtering by MIME type. When there are several error responses and they have non-overlapping content types, instead of filtering those out, it's reducing the type to never. Here's a minimal TypeScript snippet that demonstrates the issue:

type FilterKeys<Obj, Matchers> = Obj[keyof Obj & Matchers];

type Responses = {
  200: { "application/json": string; };
  401: {}
  422: { "application/json": number };
}

// this is `never`
type Result = FilterKeys<FilterKeys<Responses, 401 | 422>, "application/json">

@drwpow
Copy link
Contributor

drwpow commented Apr 8, 2024

Ah great investigation. I hadn’t dug into it yet, but that’s currently an untested code path and I could see that causing errors. Thanks for opening a PR!

@topaxi
Copy link

topaxi commented Jul 3, 2024

I came here due to #1723.

Something like this seems to do a bit better, although I'm not confident making the change for a type that is being used as much as FilterKeys 😅

type FilterKeys<Obj, Matchers> = Obj extends Obj[keyof Obj & Matchers] ? Obj[keyof Obj & Matchers] : Obj[keyof Obj & Matchers] | never;

type OkResponseCodes = 200
type ErrorResponseCodes = 401 | 422 | 500;

type Responses = {
  200: { "application/json": Record<string, string>; "text/html": string };
  401: {}
  422: { "application/json": number };
  500: { "text/html": string }
}

type OkResponses = FilterKeys<Responses, OkResponseCodes>
   //^? { "application/json": Record<string, string>; "text/html": string; }

type JSONOkResponses = FilterKeys<OkResponses, "application/json">
   //^? Record<string, string>

type HTMLOkResponses = FilterKeys<OkResponses, "text/html">
   //^? string

type ErrorResponses = FilterKeys<Responses, ErrorResponseCodes>
   //^? {} | { "application/json": string; } | { "text/html": string; }

type AnyErrorResponses = FilterKeys<ErrorResponses, `${string}/${string}`>
   //^? string | number

type JSONErrorResponses = FilterKeys<ErrorResponses, "application/json">
   //^? number

type HTMLErrorResponses = FilterKeys<ErrorResponses, "text/html">
   //^? string

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working good first issue Straightforward problem, solvable for first-time contributors without deep knowledge of the project openapi-fetch Relevant to the openapi-fetch library PRs welcome PRs are welcome to solve this issue!
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants