Skip to content

Commit

Permalink
feat(openapi-fetch): baseUrl per request (#1817)
Browse files Browse the repository at this point in the history
* feat(openapi-fetch): baseUrl per request

* chore: update docs

* chore: add changeset

* fix(openapi-fetch): lint error

* Update .changeset/happy-singers-fry.md

Co-authored-by: Martin Paucot <m.paucot@kmp.agency>

* test: fix typo

---------

Co-authored-by: Martin Paucot <m.paucot@kmp.agency>
  • Loading branch information
Gruak and kerwanp committed Aug 18, 2024
1 parent 0e42cbb commit 2a4b067
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-singers-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-fetch": minor
---

Allow specifying baseUrl per request
1 change: 1 addition & 0 deletions docs/openapi-fetch/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ client.GET("/my-url", options);
| `querySerializer` | QuerySerializer | (optional) Provide a [querySerializer](#queryserializer) |
| `bodySerializer` | BodySerializer | (optional) Provide a [bodySerializer](#bodyserializer) |
| `parseAs` | `"json"` \| `"text"` \| `"arrayBuffer"` \| `"blob"` \| `"stream"` | (optional) Parse the response using [a built-in instance method](https://developer.mozilla.org/en-US/docs/Web/API/Response#instance_methods) (default: `"json"`). `"stream"` skips parsing altogether and returns the raw stream. |
| `baseUrl` | `string` | Prefix the fetch URL with this option (e.g. "https://myapi.dev/v1/") |
| `fetch` | `fetch` | Fetch instance used for requests (default: fetch from `createClient`) |
| `middleware` | `Middleware[]` | [See docs](/openapi-fetch/middleware-auth) |
| (Fetch options) | | Any valid fetch option (`headers`, `mode`, `cache`, `signal`, …) ([docs](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options)) |
Expand Down
4 changes: 4 additions & 0 deletions packages/openapi-fetch/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export type FetchResponse<T, Options, Media extends MediaType> =

export type RequestOptions<T> = ParamsOption<T> &
RequestBodyOption<T> & {
baseUrl?: string;
querySerializer?: QuerySerializer<T> | QuerySerializerOptions;
bodySerializer?: BodySerializer<T>;
parseAs?: ParseAs;
Expand Down Expand Up @@ -292,3 +293,6 @@ export declare function createFinalURL<O>(

/** Merge headers a and b, with b taking priority */
export declare function mergeHeaders(...allHeaders: (HeadersOptions | undefined)[]): Headers;

/** Remove trailing slash from url */
export declare function removeTrailingSlash(url: string): string;
19 changes: 16 additions & 3 deletions packages/openapi-fetch/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ export default function createClient(clientOptions) {
headers: baseHeaders,
...baseOptions
} = { ...clientOptions };
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
}
baseUrl = removeTrailingSlash(baseUrl);
baseHeaders = mergeHeaders(DEFAULT_HEADERS, baseHeaders);
const middlewares = [];

Expand All @@ -53,6 +51,7 @@ export default function createClient(clientOptions) {
*/
async function coreFetch(schemaPath, fetchOptions) {
const {
baseUrl: localBaseUrl,
fetch = baseFetch,
headers,
params = {},
Expand All @@ -61,6 +60,9 @@ export default function createClient(clientOptions) {
bodySerializer = globalBodySerializer ?? defaultBodySerializer,
...init
} = fetchOptions || {};
if (localBaseUrl) {
baseUrl = removeTrailingSlash(localBaseUrl);
}

let querySerializer =
typeof globalQuerySerializer === "function"
Expand Down Expand Up @@ -563,3 +565,14 @@ export function mergeHeaders(...allHeaders) {
}
return finalHeaders;
}

/**
* Remove trailing slash from url
* @type {import("./index.js").removeTrailingSlash}
*/
export function removeTrailingSlash(url) {
if (url.endsWith("/")) {
return url.substring(0, url.length - 1);
}
return url;
}
50 changes: 50 additions & 0 deletions packages/openapi-fetch/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,29 @@ describe("client", () => {
expect(getRequestUrl().href).toBe(toAbsoluteURL("/self"));
});

it("baseUrl per request", async () => {
const localBaseUrl = "https://api.foo.bar/v1";
let client = createClient<paths>({ baseUrl });

const { getRequestUrl } = useMockRequestHandler({
baseUrl: localBaseUrl,
method: "get",
path: "/self",
status: 200,
body: { message: "OK" },
});

await client.GET("/self", { baseUrl: localBaseUrl });

// assert baseUrl and path mesh as expected
expect(getRequestUrl().href).toBe(toAbsoluteURL("/self", localBaseUrl));

client = createClient<paths>({ baseUrl });
await client.GET("/self", { baseUrl: localBaseUrl });
// assert trailing '/' was removed
expect(getRequestUrl().href).toBe(toAbsoluteURL("/self", localBaseUrl));
});

describe("headers", () => {
it("persist", async () => {
const headers: HeadersInit = { Authorization: "Bearer secrettoken" };
Expand Down Expand Up @@ -1282,6 +1305,33 @@ describe("client", () => {
expect(req.headers.get("onFetch")).toBe("exists");
expect(req.headers.get("onRequest")).toBe("exists");
});

it("baseUrl can be overridden", async () => {
useMockRequestHandler({
baseUrl: "https://api.foo.bar/v1/",
method: "get",
path: "/self",
status: 200,
body: {},
});

let requestBaseUrl = "";

const client = createClient<paths>({
baseUrl,
});
client.use({
onRequest({ options }) {
requestBaseUrl = options.baseUrl;
return undefined;
},
});

await client.GET("/self", {
baseUrl: "https://api.foo.bar/v1/",
});
expect(requestBaseUrl).toBe("https://api.foo.bar/v1");
});
});
});

Expand Down

0 comments on commit 2a4b067

Please sign in to comment.