fabien0102 / openapi-codegen

A tool for generating code base on an OpenAPI schema.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Customizing the return type of generated hooks

kallebornemark opened this issue · comments

My backend is passing an etag header when I fetch data. I can access this in <namspace>Fetcher.ts with response.headers.get("etag"), and if I include this in the return object, the fetcher's return type is correctly inferred in <namspace>Components.ts.

How do I access this in the generated React Query hook consuming this fetcher? From what I can tell, these hooks always return the same data as the endpoint in the Swagger spec, and I haven't found a way of configuring this.

A solution would of course be to use the fetcher directly, but that kind of defeats the point of generating the hook.

Hi 👋

Indeed, after playing with your usecase locally, this is not as trivial as thought 😕

A solution (a bit hacky) will be to add the definition of this etag part of every response in the openapi specs, and inject it later on.

diff --git a/openapi-codegen.config.ts b/openapi-codegen.config.ts
index 3035058..dfe1c91 100644
--- a/openapi-codegen.config.ts
+++ b/openapi-codegen.config.ts
@@ -11,6 +11,17 @@ export default defineConfig({
     },
     outputDir: "src/petstore",
     to: async (context) => {
+      Object.entries(context.openAPIDocument.components!.schemas!).forEach(
+        ([_key, value]) => {
+          if ("$ref" in value) return;
+
+          value.properties!.$etag = {
+            type: "string",
+            description: "etag header",
+          };
+        }
+      );
+
       const filenamePrefix = "petstore";
       const { schemasFiles } = await generateSchemaTypes(context, {
         filenamePrefix,

So when I’m generating, I now have this type in all my components:
image

And I can retrieve the value
image

Let’s not forget to inject the value in our Fetcher:

--- a/src/petstore/petstoreFetcher.ts
+++ b/src/petstore/petstoreFetcher.ts
@@ -89,7 +89,10 @@ export async function petstoreFetch<
     }
 
     if (response.headers.get("content-type")?.includes("json")) {
-      return await response.json();
+      return {
+        ...(await response.json()),
+        $etag: response.headers.get("etag"),
+      };
     } else {
       // if it is not a json response, assume it is a blob and cast it to TData
       return (await response.blob()) as unknown as TData;

Please note that this is very hacky, this is for example not working for responses that return an array in my dummy example.

I will leave this issue open, we can definitely do better than this! I hope this is helping a bit

I appreciate you looking into this! 🙏 What you suggested is similar to the hack I attempted myself.

I've currently changed approach slightly by saving the ETag to localStorage at the fetcher level, removing the need to read this value at the hook consumer level.

So I'd consider my original issue resolved, but I understand if you want to keep it open for future development purposes.

Good to know that your issue is solved! I’m wondering why localStorage, it seems more than a global value (but I’m missing a lot of contexts ^^)

Just in case, you can just do window.etag = response.headers.get("etag") and type the window object like this:

declare global {
  interface Window {
    /**
     * Value from `etag` header
     */
    etag?: string
  }
}

It just seemed reasonable for my use-case as I'm already storing some fetch-related things there. Do you see any benefits of using window here?

Some benefits I can see:

  • You can type it
  • This is less prone to user modifications

But nothing critical 😉