hey-api / openapi-ts

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

Parameters with `in: header` cause incompatible types #612

Closed cathywise closed 1 month ago

cathywise commented 1 month ago

Description

I have an OpenAPI spec where there is a custom header parameter in one of the requests, which is a JSON format. The generated type is however, unknown, which makes it tricky to use. In fact, when used with the client @hey-api/client-fetch, the generated service code doesn't compile.

For example, with the spec and config below, the services.gen.ts is:

// This file is auto-generated by @hey-api/openapi-ts

import { client, type Options } from '@hey-api/client-fetch';
import type { AddPetData, AddPetError, AddPetResponse } from './types.gen';

export class PetService {
    /**
     * Add a new pet to the store
     * Add a new pet to the store
     */
    public static addPet(options: Options<AddPetData>) {
        return (options?.client ?? client).post<AddPetResponse, AddPetError>({
            ...options,
            url: '/pet'
        });
    }

And in types.gen.ts is:

export type AddPetData = {
    /**
     * Create a new pet in the store
     */
    body: Pet;
    headers: {
        petMetadata: unknown;
    };
};

This type isn't compatible with RequestOptionsBase. The full error from the compiler is:

Argument of type '{ url: string; method?: "POST" | "GET" | "CONNECT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH" | "PUT" | "TRACE" | undefined; signal?: AbortSignal | null | undefined; cache?: RequestCache | undefined; ... 18 more ...; headers: { ...; }; }' is not assignable to parameter of type 'RequestOptionsBase'.
  Types of property 'headers' are incompatible.
    Type '{ petMetadata: unknown; }' is not assignable to type 'HeadersInit | Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined> | undefined'.
      Type '{ petMetadata: unknown; }' is not assignable to type 'Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined>'.
        Property 'petMetadata' is incompatible with index signature.
          Type 'unknown' is not assignable to type 'string | number | boolean | (string | number | boolean)[] | null | undefined'.ts(2345)

Please let me know if there's any more info I can provide to help!

OpenAPI specification (optional)

openapi: 3.0.3
info:
  title: Swagger Petstore - OpenAPI 3.0
  version: 1.0.11
servers:
  - url: https://petstore3.swagger.io/api/v3
paths:
  /pet:

    post:
      tags:
        - pet
      summary: Add a new pet to the store
      description: Add a new pet to the store
      operationId: addPet
      parameters:
      - content:
          application/json:
            schema:
              $ref: '#/components/schemas/PetMetadata'
        in: header
        name: Pet-Metadata
        required: true
      requestBody:
        description: Create a new pet in the store
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
          application/xml:
            schema:
              $ref: '#/components/schemas/Pet'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/Pet'
        required: true
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
            application/xml:
              schema:
                $ref: '#/components/schemas/Pet'
        '400':
          description: Invalid input
        '422':
          description: Validation exception
      security:
        - petstore_auth:
            - write:pets
            - read:pets

components:
  schemas:
    PetMetadata:
      type: object
      properties:
        breed:
          type: string
    Pet:
      required:
        - name
        - photoUrls
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        name:
          type: string
          example: doggie
        photoUrls:
          type: array
          xml:
            wrapped: true
          items:
            type: string
            xml:
              name: photoUrl
        status:
          type: string
          description: pet status in the store
          enum:
            - available
            - pending
            - sold
      xml:
        name: pet
    ApiResponse:
      type: object
      properties:
        code:
          type: integer
          format: int32
        type:
          type: string
        message:
          type: string
      xml:
        name: '##default'
  requestBodies:
    Pet:
      description: Pet object that needs to be added to the store
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Pet'
        application/xml:
          schema:
            $ref: '#/components/schemas/Pet'
  securitySchemes:
    petstore_auth:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: https://petstore3.swagger.io/oauth/authorize
          scopes:
            write:pets: modify pets in your account
            read:pets: read your pets
    api_key:
      type: apiKey
      name: api_key
      in: header

Configuration

import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
  input: '../openapi/petstore.yaml',
  client: '@hey-api/client-fetch',
  output: {
    path: './ts',
    format: 'prettier',
    lint: 'eslint',
  },
  services: {
    asClass: true,
  },
  types: {
    enums: "javascript",
  }
});

System information (optional)

MacOS, Node v20.10.0, NPM 10.2.3

mrlubos commented 1 month ago

Thanks for the issue @cathywise! Sounds like I should be able to easily reproduce and fix. Thanks for trying out the new client! 🚀

mrlubos commented 1 month ago

Hey @cathywise, I've got a fix for this which I will publish shortly. There's no need to update the fetch client, fixing types worked for me, no more compiler problems (that is, unless I did NOT include the header which is the expected behaviour).

It's a bit of a naïve fix as it's not tested on multiple content types. If you'll still have problems, please open another issue. I just worry if that happens, it will take longer to resolve, because the types don't support multiple content types well. That's something I need to eventually improve, hopefully a worry for another day.

mrlubos commented 1 month ago

Pushed another update which uses JSON.stringify() to convert the value when making the actual request. Please update both the client and @hey-api/openapi-ts to the latest version and let me know if you run into more problems!

cathywise commented 1 month ago

Thanks! The fix is working a treat 💟