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

Allow onRequest middleware to add headers that the schema requires #2073

Open
jnatmorris opened this issue Dec 28, 2024 · 2 comments
Open

Allow onRequest middleware to add headers that the schema requires #2073

jnatmorris opened this issue Dec 28, 2024 · 2 comments
Assignees
Labels
enhancement New feature or request openapi-fetch Relevant to the openapi-fetch library openapi-fetch-codegen Requires codegen for openapi-fetch

Comments

@jnatmorris
Copy link

jnatmorris commented Dec 28, 2024

To my understanding, if one has multiple protected routes, say /auth/*, which are specified in the schema and require an Authorization header, currently it is required to set the header with each request.

const authRequest1 = await client.GET("/auth/sample1", {
	params: {
		header: {
			authorization: `Bearer ${await someAuthFunc()}`
		}
	}
});

const authRequest2 = await client.GET("/auth/sample2", {
	params: {
		header: {
			authorization: `Bearer ${await someAuthFunc()}`
		}
	}
});

While the current implementation of middleware allows for setting a request header, TS still requires an authorization to be set with each individual request. So doing the following becomes not possible.

import createClient, { type Middleware } from "openapi-fetch";
import type { paths } from "./my-openapi-3-schema";

const authMiddleware: Middleware = {
  async onRequest({ request }) {
    request.headers.set("authorization", `Bearer ${await someAuthFunc()}`);
    return request;
  },
};

const client = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });
client.use(authMiddleware);

// TS errors as no authorization header is added
const authRequest1 = await client.GET("/auth/sample1");
const authRequest2 = await client.GET("/auth/sample2");

While this does not invalidate many of the use cases for middleware, which are great, this use case would be a welcome addition.

@jnatmorris jnatmorris added enhancement New feature or request openapi-fetch Relevant to the openapi-fetch library labels Dec 28, 2024
@drwpow drwpow self-assigned this Jan 3, 2025
@drwpow drwpow added the openapi-fetch-codegen Requires codegen for openapi-fetch label Jan 3, 2025
@drwpow
Copy link
Contributor

drwpow commented Jan 3, 2025

This is tricky because there’s no way to store that information in a way that’s currently-accessible by the static types. You’ve stated a clear problem that all users of this library have, and would be a good improvement! But your description doesn’t outline enough of a proposal here to describe exactly how to solve this problem. I’d love to see this expanded into more of a complete solution that describes how we could achieve this—how can static typing “peer into” what you have or have not done in middleware?

As an aside, I’ve added a new “openapi-fetch-codegen” label to this issue, because I’m not sure that this would even be possible without having some sort of intermediary codegen step for openapi-fetch. But perhaps it is, and I’m just not thinking of it! Either way, there are a number of related issues that currently don’t have known solutions for. Like everything, it‘s a tradeoff—openapi-fetch relies on no codegen, and some of the absolute fastest performance possible by its design. But this is one of the tradeoffs—repetition in runtime code like this. If users are willing to sacrifice a little more memory usage, and a little more footprint, and a codegen step, we could come up with solutions.

@Valahaar
Copy link

Valahaar commented Feb 6, 2025

Hi! Jumping in to provide at least a temporary workaround that lets our linters sleep easy :)

Remove authorization header directly

import createClient, { type Middleware } from "openapi-fetch";
import type { paths } from "./my-openapi-3-schema";

const authMiddleware: Middleware = {
  async onRequest({ request }) {
    request.headers.set("authorization", `Bearer ${await someAuthFunc()}`);
    return request;
  },
};

type RemoveAuthHeader<PathSchema> = {
    [Method in keyof PathSchema]: PathSchema[Method] extends {
        parameters: { header: { 'authorization': unknown } }
    } ? Omit<PathSchema[Method], 'parameters'> & {
        parameters: Omit<PathSchema[Method]['parameters'], 'header'> & {
            header?: Omit<PathSchema[Method]['parameters']['header'], 'authorization'>
        }
    } : PathSchema[Method]
};

type NoAuthPaths = {
    [Path in keyof paths]: RemoveAuthHeader<paths[Path]>
};

const client = createClient<NoAuthPaths>({ baseUrl: "https://myapi.dev/v1/" });
client.use(authMiddleware);

// TS should not complain anymore
const authRequest1 = await client.GET("/auth/sample1");
const authRequest2 = await client.GET("/auth/sample2");

Specify anything to remove

This solution is quite more complex as it involves getting a configuration for parameters you want to remove. It's useful if you have other params you specify in your middleware - be them cookies, headers, query params, or path params.

type RemoveParamsConfig = {
    header?: string;
    query?: string;
    path?: string;
    cookie?: string;
};

type PatchParam<T, Config extends RemoveParamsConfig, P extends keyof Config> = T extends { [key in P]: infer H }
    ? H extends Partial<Record<string, unknown>>
    ? Config[P] extends string
    ? Omit<H, Config[P]>
    : H
    : never
    : never;

type RemoveParams<MethodSchema, Config extends RemoveParamsConfig> =
    MethodSchema extends { parameters: infer P }
    ? Omit<MethodSchema, 'parameters'> & {
        parameters: {
            header?: PatchParam<P, Config, 'header'>;
            query?: PatchParam<P, Config, 'query'>;
            path?: PatchParam<P, Config, 'path'>;
            cookie?: PatchParam<P, Config, 'cookie'>;
        }
    }
    : MethodSchema;

# here you define the parts you wish to ignore
type ToRemove = { header: 'authorization', query: 'param1' | 'param2' };

type RemoveAuthHeader<PathSchema> = {
    [Method in keyof PathSchema]: RemoveParams<PathSchema[Method], ToRemove>
};

type NoAuthPaths = {
    [Path in keyof paths]: RemoveAuthHeader<paths[Path]>
};

export const client = createClient<NoAuthPaths>({
    baseUrl: process.env.SERVER_BACKEND!,
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request openapi-fetch Relevant to the openapi-fetch library openapi-fetch-codegen Requires codegen for openapi-fetch
Projects
None yet
Development

No branches or pull requests

3 participants