OpenAPI-Qraft / openapi-qraft

Generate a type-safe TanStack Query React ✨ client from your OpenAPI document
https://openapi-qraft.github.io/openapi-qraft/
MIT License
31 stars 3 forks source link

Ability to make certain headers optional #90

Closed TomCaserta closed 2 months ago

TomCaserta commented 3 months ago

Hey first of all, thanks for creating this library! It works very well so far in my testing and saves a ton of time!

To explain the title further, for context, I have an openapi json (not controlled by me) which specifies some required headers for every request. Each request requires a custom header to be specified and its the same value across all requests. To make things easier I decided I would set the header globally in the requestFn much like you do for the 'Authorization' header in this docs page:

https://openapi-qraft.github.io/openapi-qraft/docs/hooks/qraft-context

The issue is, since the header is specified as required in the parameters of the operation in the openapi json the generated types for the hooks also require this header.

I am wondering if its possible to somehow add an option when generating the client hook typings to make a certain header optional?

As a work around I am aware I could pre-process the schema to change the header from required to optional but I believe this may be useful if theres an accepted way to handle this situation for users of this library.

Thanks,

TomCaserta commented 3 months ago

Ah nice, I think I understand now how the library expects you to handle this.

On the cli it appears we can set the --operation-generics-import-path option to import from our own ServiceOperationMutation / ServiceOperationQuery types allowing developers to perform any required modifications to the types.

Am I correct in this being the accepted way to achieve what I want to achieve?

This is what I ended up with, a file outside of the generated files directory which contains the following type definitions:

import type { DefaultError } from '@tanstack/query-core';

import type {
    ServiceOperationQuery as OriginalServiceOperationQuery,
    ServiceOperationMutation as OriginalServiceOperationMutation,
} from '@openapi-qraft/react';

type HeadersToRemove = 'X-My-Custom-Header';
type RemoveHeaders<Headers> = Omit<Headers, HeadersToRemove>;

type ModifyParams<Params> = Params extends { header: { [P in HeadersToRemove]?: string } }
    ? keyof RemoveHeaders<Params['header']> extends never
        ? Omit<Params, 'header'>
        : Omit<Params, 'header'> & { header: RemoveHeaders<Params['header']> }
    : Params;

export type ServiceOperationQuery<
    TSchema extends { url: string; method: string },
    TData,
    TParams,
    TError = DefaultError,
> = OriginalServiceOperationQuery<TSchema, TData, ModifyParams<TParams>, TError>;

export type ServiceOperationMutation<
    TSchema extends { url: string; method: string },
    TBody,
    TData,
    TParams,
    TError = DefaultError,
> = OriginalServiceOperationMutation<TSchema, TBody, TData, ModifyParams<TParams>, TError>;

then when calling the CLI I specified the --operation-generics-import-path option as the path to the typescript file containing these types.

radist2s commented 3 months ago

@TomCaserta, you're right about wanting to set certain parameters in advance. However, redefining ServiceOperationQuery won't help here.

The problem is that the parameters will still reside in schema.d.ts, which is generated by openapi-typescript, and they will constantly get in the way.

For this reason, I decided to generate schema.d.ts using @openapi-qraft/openapi-typescript-plugin as a thin wrapper over openapi-typescript. This allows to add functionality like support for the --operation-predefined-parameter option 🆕

This feature is now available in @openapi-qraft/react@1.11.0-beta.1. If you can't wait, you can try @openapi-qraft/{react,cli}@1.11.0-beta.1 (next betas break support for Next.js Server Actions temporary). Use the -h option to display the help.

An example of using --operation-predefined-parameter is here. This option allows you to make the specified parameters optional. For example,

--operation-predefined-parameters `/**:header.Authorization` 

makes the Authorization header optional for all requests:

// schema.d.ts
parameters: {
  query?: never;
  header?: {
    Authorization?: string;
  };
  path: {
    approval_request_id: string;
  };
};

This automatically simplifies working with parameter types in the code, and consequently, Qraft won't require them to be passed in the request.

You've already figured out how to add headers, but I would also recommend implementing a check to ensure they are needed for the request, like here.

A crucial point: if even one endpoint does not contain header.Authorization in its parameters, such as /public-profile, you must specify it as an exception:

--operation-predefined-parameters `/**,!/public-profile:header.Authorization` 

In future versions, there will be automatic resolution of which predefined parameter is needed for which request, so you won't need to write regular expressions or worry about headers ending up where they shouldn't be.