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

Added Custom Headers Logic to Query #9458

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions src/Utils/request/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,40 @@ import careConfig from "@careConfig";

import { QueryError } from "@/Utils/request/queryError";
import { getResponseBody } from "@/Utils/request/request";
import { QueryOptions, Route } from "@/Utils/request/types";
import { Route } from "@/Utils/request/types";
// Remove conflicting QueryOptions import
import { makeHeaders, makeUrl } from "@/Utils/request/utils";

// Define QueryOptions locally, including the 'headers' property
export interface QueryOptions<TBody> {
body?: TBody;
queryParams?: Record<string, any>;
pathParams?: Record<string, any>;
silent?: boolean;
signal?: AbortSignal;
headers?: Record<string, string>; // Add headers support
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use HeadersInit type for better compatibility with the Fetch API.

The headers property type should be HeadersInit instead of Record<string, string> for better compatibility with the Fetch API's Headers interface.

Apply this diff:

  export interface QueryOptions<TBody> {
    body?: TBody;
    queryParams?: Record<string, any>;
    pathParams?: Record<string, any>;
    silent?: boolean;
    signal?: AbortSignal;
-   headers?: Record<string, string>; // Add headers support
+   headers?: HeadersInit; // Add headers support
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface QueryOptions<TBody> {
body?: TBody;
queryParams?: Record<string, any>;
pathParams?: Record<string, any>;
silent?: boolean;
signal?: AbortSignal;
headers?: Record<string, string>; // Add headers support
}
export interface QueryOptions<TBody> {
body?: TBody;
queryParams?: Record<string, any>;
pathParams?: Record<string, any>;
silent?: boolean;
signal?: AbortSignal;
headers?: HeadersInit; // Add headers support
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not duplicate the type. this is already present in request/types.ts


async function queryRequest<TData, TBody>(
{ path, method, noAuth }: Route<TData, TBody>,
options?: QueryOptions<TBody>,
): Promise<TData> {
const url = `${careConfig.apiUrl}${makeUrl(path, options?.queryParams, options?.pathParams)}`;

// Convert Headers object to a plain object
const defaultHeaders = Object.fromEntries(
makeHeaders(noAuth ?? false).entries(),
);

// Merge default headers with custom headers, with custom headers taking precedence
const headers = {
...defaultHeaders,
...(options?.headers || {}),
};
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve header handling using the Headers API.

The current implementation converts Headers to a plain object and then merges them, which:

  1. Loses the built-in validation and normalization of the Headers API
  2. Makes it harder to handle multiple values for the same header

Consider using the Headers API directly:

-  // Convert Headers object to a plain object
-  const defaultHeaders = Object.fromEntries(
-    makeHeaders(noAuth ?? false).entries(),
-  );
-
-  // Merge default headers with custom headers, with custom headers taking precedence
-  const headers = {
-    ...defaultHeaders,
-    ...(options?.headers || {}),
-  };
+  // Use Headers API for better header management
+  const headers = new Headers(makeHeaders(noAuth ?? false));
+  
+  // Merge custom headers
+  if (options?.headers) {
+    const customHeaders = new Headers(options.headers);
+    customHeaders.forEach((value, key) => {
+      headers.set(key, value);
+    });
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Convert Headers object to a plain object
const defaultHeaders = Object.fromEntries(
makeHeaders(noAuth ?? false).entries(),
);
// Merge default headers with custom headers, with custom headers taking precedence
const headers = {
...defaultHeaders,
...(options?.headers || {}),
};
// Use Headers API for better header management
const headers = new Headers(makeHeaders(noAuth ?? false));
// Merge custom headers
if (options?.headers) {
const customHeaders = new Headers(options.headers);
customHeaders.forEach((value, key) => {
headers.set(key, value);
});
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any performance overhead introduced by using the Headers API compared to a plain object?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srijantrpth, using the Headers API introduces minimal performance overhead compared to using a plain object. The Headers API is optimized for managing HTTP headers efficiently, and it provides benefits like built-in normalization and validation. These features enhance the robustness of header handling without significantly impacting performance. In most cases, the slight overhead is negligible and is outweighed by the advantages in code maintainability and correctness.

If performance is a critical concern, we can consider benchmarking both implementations to measure any differences. However, it's unlikely to be a bottleneck under normal circumstances.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it used across all our environments? If yes, let me rectify it else it's fine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srijantrpth, the Headers API is indeed used in our codebase. Specifically, in src/Utils/request/utils.ts, we create headers using the Headers API:

const headers = new Headers({
  "Content-Type": "application/json",
  Accept: "application/json",
  ...additionalHeaders,
});

Using the Headers API in your implementation would maintain consistency across all our environments. It would be beneficial to adopt it for better header management.

🔗 Analysis chain
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of the Headers API in the codebase
ast-grep --pattern 'new Headers($_)'

Length of output: 324

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let me rectify the code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srijantrpth, glad to hear you're updating the code! Let me know if you need any assistance.


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!


Copy link
Member

@rithviknishad rithviknishad Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you may be confused about the objective. This would "overwrite" the headers. But the intended behaviour is to "append" the headers while preserving the previously set headers.

Let's stick with using the Headers API. It's there for a purpose :) -- best practices

Refer: https://developer.mozilla.org/en-US/docs/Web/API/Headers/append

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorry about that, I'll fix this and let you know

const fetchOptions: RequestInit = {
method,
headers: makeHeaders(noAuth ?? false),
headers,
signal: options?.signal,
};

Expand Down
1 change: 1 addition & 0 deletions src/Utils/request/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface QueryOptions<TBody = unknown> {
body?: TBody;
silent?: boolean;
signal?: AbortSignal;
headers?: HeadersInit;
}

export interface PaginatedResponse<TItem> {
Expand Down
17 changes: 4 additions & 13 deletions src/Utils/request/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,33 +50,24 @@ const ensurePathNotMissingReplacements = (path: string) => {
}
};

export function makeHeaders(noAuth: boolean) {
export function makeHeaders(noAuth: boolean, additionalHeaders?: HeadersInit) {
const headers = new Headers({
"Content-Type": "application/json",
Accept: "application/json",
...additionalHeaders,
});

if (!noAuth) {
const token = getAuthorizationHeader();
const token = localStorage.getItem(LocalStorageKeys.accessToken);

if (token) {
headers.append("Authorization", token);
headers.append("Authorization", `Bearer ${token}`);
}
}

return headers;
}

export function getAuthorizationHeader() {
const bearerToken = localStorage.getItem(LocalStorageKeys.accessToken);

if (bearerToken) {
return `Bearer ${bearerToken}`;
}

return null;
}

export function mergeRequestOptions<TData>(
options: RequestOptions<TData>,
overrides: RequestOptions<TData>,
Expand Down
Loading