Skip to content

Commit

Permalink
Merge branch 'upgrade-node-fetch' of github.com:googleapis/gaxios int…
Browse files Browse the repository at this point in the history
…o native-fetch
  • Loading branch information
danielbankhead committed May 16, 2024
2 parents 2e389c8 + e6c2371 commit a8b2a3d
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 133 deletions.
119 changes: 37 additions & 82 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class GaxiosError<T = any> extends Error {

constructor(
message: string,
public config: GaxiosOptions,
public config: GaxiosOptionsPrepared,
public response?: GaxiosResponse<T>,
public error?: Error | NodeJS.ErrnoException
) {
Expand Down Expand Up @@ -111,45 +111,43 @@ export class GaxiosError<T = any> extends Error {
}

if (config.errorRedactor) {
config.errorRedactor<T>({
config.errorRedactor({
config: this.config,
response: this.response,
});
}
}
}

/**
* @deprecated use native {@link globalThis.Headers}.
*/
export interface Headers {
[index: string]: any;
}
export type GaxiosPromise<T = any> = Promise<GaxiosResponse<T>>;
type GaxiosResponseData =
| ReturnType<JSON['parse']>
| GaxiosOptionsPrepared['data'];

export type GaxiosPromise<T = GaxiosResponseData> = Promise<GaxiosResponse<T>>;

export interface GaxiosResponse<T = any> extends Response {
config: GaxiosOptions;
export interface GaxiosResponse<T = GaxiosResponseData> extends Response {
config: GaxiosOptionsPrepared;
data: T;
}

export interface GaxiosMultipartOptions {
headers: Headers;
headers: HeadersInit;
content: string | Readable;
}

/**
* Request options that are used to form the request.
*/
export interface GaxiosOptions extends Omit<RequestInit, 'headers'> {
export interface GaxiosOptions extends RequestInit {
/**
* Optional method to override making the actual HTTP request. Useful
* for writing tests.
*
* @deprecated Use {@link GaxiosOptions.fetchImplementation} instead.
*/
adapter?: <T = any>(
options: GaxiosOptions,
defaultAdapter: (options: GaxiosOptions) => GaxiosPromise<T>
adapter?: <T = GaxiosResponseData>(
options: GaxiosOptionsPrepared,
defaultAdapter: (options: GaxiosOptionsPrepared) => GaxiosPromise<T>
) => GaxiosPromise<T>;
url?: string | URL;
/**
Expand All @@ -167,16 +165,6 @@ export interface GaxiosOptions extends Omit<RequestInit, 'headers'> {
| 'OPTIONS'
| 'TRACE'
| 'PATCH';
/**
* Recommended: Provide a native {@link globalThis.Headers Headers} object.
*
* @privateRemarks
*
* This type does not have the native {@link globalThis.Headers Headers} in
* its signature as it would break customers looking to modify headers before
* providing to this library (new, unnecessary type checks/guards).
*/
headers?: Headers;
/**
* The data to send in the {@link RequestInit.body} of the request. Objects will be
* serialized as JSON, except for:
Expand Down Expand Up @@ -308,24 +296,11 @@ export interface GaxiosOptions extends Omit<RequestInit, 'headers'> {
*/
errorRedactor?: typeof defaultErrorRedactor | false;
}
/**
* A partial object of `GaxiosOptions` with only redactable keys
*
* @experimental
*/
export type RedactableGaxiosOptions = Pick<
GaxiosOptions,
'body' | 'data' | 'headers' | 'url'
>;
/**
* A partial object of `GaxiosResponse` with only redactable keys
*
* @experimental
*/
export type RedactableGaxiosResponse<T = any> = Pick<
GaxiosResponse<T>,
'config' | 'data' | 'headers'
>;

export interface GaxiosOptionsPrepared extends GaxiosOptions {
headers: globalThis.Headers;
url: URL;
}

/**
* Configuration for the Gaxios `request` method.
Expand Down Expand Up @@ -381,7 +356,10 @@ export interface RetryConfig {
retryBackoff?: (err: GaxiosError, defaultBackoffMs: number) => Promise<void>;
}

function translateData(responseType: string | undefined, data: any) {
function translateData(
responseType: string | undefined,
data: GaxiosResponseData
) {
switch (responseType) {
case 'stream':
return data;
Expand All @@ -404,51 +382,30 @@ function translateData(responseType: string | undefined, data: any) {
*
* @experimental
*/
export function defaultErrorRedactor<T = any>(data: {
config?: RedactableGaxiosOptions;
response?: RedactableGaxiosResponse<T>;
}) {
export function defaultErrorRedactor<
O extends GaxiosOptionsPrepared,
R extends GaxiosResponse<GaxiosResponseData>,
>(data: {config?: O; response?: R}) {
const REDACT =
'<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.';

function redactHeaders(headers?: Headers | globalThis.Headers) {
function redactHeaders(headers?: Headers) {
if (!headers) return;

function check(key: string) {
headers.forEach((_, key) => {
// any casing of `Authentication`
// any casing of `Authorization`
// anything containing secret, such as 'client secret'
return (
if (
/^authentication$/i.test(key) ||
/^authorization$/i.test(key) ||
/secret/i.test(key)
);
}

function redactHeadersObject(headers: Headers) {
for (const key of Object.keys(headers)) {
if (check(key)) headers[key] = REDACT;
}
}

function redactHeadersHeaders(headers: globalThis.Headers) {
headers.forEach((value, key) => {
if (check(key)) headers.set(key, REDACT);
});
}

// support `node-fetch` Headers and other third-parties
if (headers instanceof Headers || 'set' in headers) {
redactHeadersHeaders(headers as globalThis.Headers);
} else {
redactHeadersObject(headers);
}
)
headers.set(key, REDACT);
});
}

function redactString<T extends GaxiosOptions | RedactableGaxiosResponse>(
obj: T,
key: keyof T
) {
function redactString<T extends O | R>(obj: T, key: keyof T) {
if (
typeof obj === 'object' &&
obj !== null &&
Expand All @@ -466,9 +423,7 @@ export function defaultErrorRedactor<T = any>(data: {
}
}

function redactObject<T extends GaxiosOptions['data'] | GaxiosResponse>(
obj: T | null
) {
function redactObject<T extends O['data'] | R>(obj: T | null) {
if (!obj) {
return;
} else if (
Expand Down Expand Up @@ -507,7 +462,7 @@ export function defaultErrorRedactor<T = any>(data: {
redactObject(data.config.body);

try {
const url = new URL('', data.config.url);
const url = data.config.url;

if (url.searchParams.has('token')) {
url.searchParams.set('token', REDACT);
Expand All @@ -517,7 +472,7 @@ export function defaultErrorRedactor<T = any>(data: {
url.searchParams.set('client_secret', REDACT);
}

data.config.url = url.toString();
data.config.url = url;
} catch {
// ignore error - no need to parse an invalid URL
}
Expand Down
56 changes: 27 additions & 29 deletions src/gaxios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
GaxiosMultipartOptions,
GaxiosError,
GaxiosOptions,
GaxiosOptionsPrepared,
GaxiosPromise,
GaxiosResponse,
Headers,
defaultErrorRedactor,
} from './common';
import {getRetryConfig} from './retry';
Expand All @@ -49,7 +49,7 @@ export class Gaxios {
* Interceptors
*/
interceptors: {
request: GaxiosInterceptorManager<GaxiosOptions>;
request: GaxiosInterceptorManager<GaxiosOptionsPrepared>;
response: GaxiosInterceptorManager<GaxiosResponse>;
};

Expand All @@ -76,11 +76,11 @@ export class Gaxios {
}

private async _defaultAdapter<T>(
config: GaxiosOptions
config: GaxiosOptionsPrepared
): Promise<GaxiosResponse<T>> {
const fetchImpl = config.fetchImplementation || fetch;

const res = await fetchImpl(config.url!, config);
const res = await fetchImpl(config.url, config);
const data = await this.getResponseData(config, res);

return Object.assign(res, {config, data}) as GaxiosResponse<T>;
Expand All @@ -91,7 +91,7 @@ export class Gaxios {
* @param opts Set of HTTP options that will be used for this HTTP request.
*/
protected async _request<T = any>(
opts: GaxiosOptions = {}
opts: GaxiosOptionsPrepared
): GaxiosPromise<T> {
try {
let translatedResponse: GaxiosResponse<T>;
Expand Down Expand Up @@ -143,7 +143,7 @@ export class Gaxios {
}

private async getResponseData(
opts: GaxiosOptions,
opts: GaxiosOptionsPrepared,
res: Response
): Promise<any> {
if (
Expand Down Expand Up @@ -177,7 +177,7 @@ export class Gaxios {

#urlMayUseProxy(
url: string | URL,
noProxy: GaxiosOptions['noProxy'] = []
noProxy: GaxiosOptionsPrepared['noProxy'] = []
): boolean {
const candidate = new URL(url);
const noProxyList = [...noProxy];
Expand Down Expand Up @@ -225,21 +225,21 @@ export class Gaxios {
* Applies the request interceptors. The request interceptors are applied after the
* call to prepareRequest is completed.
*
* @param {GaxiosOptions} options The current set of options.
* @param {GaxiosOptionsPrepared} options The current set of options.
*
* @returns {Promise<GaxiosOptions>} Promise that resolves to the set of options or response after interceptors are applied.
* @returns {Promise<GaxiosOptionsPrepared>} Promise that resolves to the set of options or response after interceptors are applied.
*/
async #applyRequestInterceptors(
options: GaxiosOptions
): Promise<GaxiosOptions> {
options: GaxiosOptionsPrepared
): Promise<GaxiosOptionsPrepared> {
let promiseChain = Promise.resolve(options);

for (const interceptor of this.interceptors.request.values()) {
if (interceptor) {
promiseChain = promiseChain.then(
interceptor.resolved,
interceptor.rejected
) as Promise<GaxiosOptions>;
) as Promise<GaxiosOptionsPrepared>;
}
}

Expand All @@ -250,9 +250,9 @@ export class Gaxios {
* Applies the response interceptors. The response interceptors are applied after the
* call to request is made.
*
* @param {GaxiosOptions} options The current set of options.
* @param {GaxiosOptionsPrepared} options The current set of options.
*
* @returns {Promise<GaxiosOptions>} Promise that resolves to the set of options or response after interceptors are applied.
* @returns {Promise<GaxiosOptionsPrepared>} Promise that resolves to the set of options or response after interceptors are applied.
*/
async #applyResponseInterceptors(
response: GaxiosResponse | Promise<GaxiosResponse>
Expand All @@ -277,8 +277,10 @@ export class Gaxios {
* @param options The original options passed from the client.
* @returns Prepared options, ready to make a request
*/
async #prepareRequest(options: GaxiosOptions): Promise<GaxiosOptions> {
const opts: GaxiosOptions = extend(true, {}, this.defaults, options);
async #prepareRequest(
options: GaxiosOptions
): Promise<GaxiosOptionsPrepared> {
const opts = extend(true, {}, this.defaults, options);
if (!opts.url) {
throw new Error('URL is required.');
}
Expand Down Expand Up @@ -432,18 +434,10 @@ export class Gaxios {
(opts as {duplex: string}).duplex = 'half';
}

// preserve the original type for auditing later
if (opts.headers instanceof Headers) {
opts.headers = preparedHeaders;
} else {
const headers: Headers = {};
preparedHeaders.forEach((value, key) => {
headers[key] = value;
});
opts.headers = headers;
}

return opts;
return Object.assign(opts, {
headers: preparedHeaders,
url: opts.url instanceof URL ? opts.url : new URL(opts.url),
});
}

/**
Expand Down Expand Up @@ -498,8 +492,12 @@ export class Gaxios {
) {
const finale = `--${boundary}--`;
for (const currentPart of multipartOptions) {
const headers =
currentPart.headers instanceof Headers
? currentPart.headers
: new Headers(currentPart.headers);
const partContentType =
currentPart.headers['Content-Type'] || 'application/octet-stream';
headers.get('Content-Type') || 'application/octet-stream';
const preamble = `--${boundary}\r\nContent-Type: ${partContentType}\r\n\r\n`;
yield preamble;
if (typeof currentPart.content === 'string') {
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export {
GaxiosError,
GaxiosPromise,
GaxiosResponse,
Headers,
GaxiosOptionsPrepared,
RetryConfig,
} from './common';
export {Gaxios, GaxiosOptions};
Expand Down
Loading

0 comments on commit a8b2a3d

Please sign in to comment.