Skip to content

Commit

Permalink
wip convert Resource.do to use promises
Browse files Browse the repository at this point in the history
Resolves #1528.
  • Loading branch information
lawrence-forooghian committed Jan 9, 2024
1 parent 68881bc commit 8a912d2
Showing 1 changed file with 115 additions and 124 deletions.
239 changes: 115 additions & 124 deletions src/common/lib/client/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,83 +7,69 @@ import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorin
import BaseClient from './baseclient';
import { MsgPack } from 'common/types/msgpack';
import { RequestCallbackHeaders } from 'common/types/http';
import { ErrnoException } from '../../types/http';

function withAuthDetails(
async function withAuthDetails<T>(
client: BaseClient,
headers: RequestCallbackHeaders | undefined,
params: Record<string, any>,
errCallback: Function,
opCallback: Function
) {
): Promise<ResourceResult<T>> {
// TODO having to catch errors everywhere along the way is kinda odd, maybe let's say that the only errors it doesn't throw are those from the HTTP layer, not just everything along the way
if (client.http.supportsAuthHeaders) {
Utils.whenPromiseSettles(
client.auth.getAuthHeaders(),
function (err: Error | null, authHeaders?: Record<string, string>) {
if (err) errCallback(err);
else opCallback(Utils.mixin(authHeaders!, headers), params);
}
);
const authHeaders = await client.auth.getAuthHeaders();
return opCallback(Utils.mixin(authHeaders!, headers), params);
} else {
Utils.whenPromiseSettles(
client.auth.getAuthParams(),
function (err: Error | null, authParams?: Record<string, string>) {
if (err) errCallback(err);
else opCallback(headers, Utils.mixin(authParams!, params));
}
);
const authParams = await client.auth.getAuthParams();
return opCallback(headers, Utils.mixin(authParams!, params));
}
}

function unenvelope<T>(
callback: ResourceCallback<T>,
result: ResourceResult<T>,
MsgPack: MsgPack | null,
format: Utils.Format | null
): ResourceCallback<T> {
return (err, body, outerHeaders, unpacked, outerStatusCode) => {
if (err && !body) {
callback(err);
return;
}
): ResourceResult<T> {
if (result.err && !result.body) {
return { err: result.err };
}

if (!unpacked) {
try {
body = Utils.decodeBody(body, MsgPack, format);
} catch (e) {
if (Utils.isErrorInfoOrPartialErrorInfo(e)) {
callback(e);
} else {
callback(new PartialErrorInfo(Utils.inspectError(e), null));
}
return;
let body = result.body;

if (!result.unpacked) {
try {
body = Utils.decodeBody(body, MsgPack, format);
} catch (e) {
if (Utils.isErrorInfoOrPartialErrorInfo(e)) {
return { err: e };
} else {
return { err: new PartialErrorInfo(Utils.inspectError(e), null) };
}
}
}

if (!body) {
callback(new PartialErrorInfo('unenvelope(): Response body is missing', null));
return;
}
if (!body) {
return { err: new PartialErrorInfo('unenvelope(): Response body is missing', null) };
}

const { statusCode: wrappedStatusCode, response, headers: wrappedHeaders } = body as Record<string, any>;
const { statusCode: wrappedStatusCode, response, headers: wrappedHeaders } = body as Record<string, any>;

if (wrappedStatusCode === undefined) {
/* Envelope already unwrapped by the transport */
callback(err, body, outerHeaders, true, outerStatusCode);
return;
}
if (wrappedStatusCode === undefined) {
/* Envelope already unwrapped by the transport */
return { ...result, body, unpacked: true };
}

if (wrappedStatusCode < 200 || wrappedStatusCode >= 300) {
/* handle wrapped errors */
let wrappedErr = (response && response.error) || err;
if (!wrappedErr) {
wrappedErr = new Error('Error in unenveloping ' + body);
wrappedErr.statusCode = wrappedStatusCode;
}
callback(wrappedErr, response, wrappedHeaders, true, wrappedStatusCode);
return;
if (wrappedStatusCode < 200 || wrappedStatusCode >= 300) {
/* handle wrapped errors */
let wrappedErr = (response && response.error) || result.err;
if (!wrappedErr) {
wrappedErr = new Error('Error in unenveloping ' + body);
wrappedErr.statusCode = wrappedStatusCode;
}
return { err: wrappedErr, body: response, headers: wrappedHeaders, unpacked: true, statusCode: wrappedStatusCode };
}

callback(err, response, wrappedHeaders, true, wrappedStatusCode);
};
return { err: result.err, body: response, headers: wrappedHeaders, unpacked: true, statusCode: wrappedStatusCode };
}

function paramString(params: Record<string, any>) {
Expand All @@ -100,37 +86,27 @@ function urlFromPathAndParams(path: string, params: Record<string, any>) {
return path + (params ? '?' : '') + paramString(params);
}

function logResponseHandler<T>(
callback: ResourceCallback<T>,
method: HttpMethods,
path: string,
params: Record<string, string>
): ResourceCallback {
return (err, body, headers, unpacked, statusCode) => {
if (err) {
Logger.logAction(
Logger.LOG_MICRO,
'Resource.' + method + '()',
'Received Error; ' + urlFromPathAndParams(path, params) + '; Error: ' + Utils.inspectError(err)
);
} else {
Logger.logAction(
Logger.LOG_MICRO,
'Resource.' + method + '()',
'Received; ' +
urlFromPathAndParams(path, params) +
'; Headers: ' +
paramString(headers as Record<string, any>) +
'; StatusCode: ' +
statusCode +
'; Body: ' +
(Platform.BufferUtils.isBuffer(body) ? body.toString() : body)
);
}
if (callback) {
callback(err, body as T, headers, unpacked, statusCode);
}
};
function logResult<T>(result: ResourceResult<T>, method: HttpMethods, path: string, params: Record<string, string>) {
if (result.err) {
Logger.logAction(
Logger.LOG_MICRO,
'Resource.' + method + '()',
'Received Error; ' + urlFromPathAndParams(path, params) + '; Error: ' + Utils.inspectError(result.err)
);
} else {
Logger.logAction(
Logger.LOG_MICRO,
'Resource.' + method + '()',
'Received; ' +
urlFromPathAndParams(path, params) +
'; Headers: ' +
paramString(result.headers as Record<string, any>) +
'; StatusCode: ' +
result.statusCode +
'; Body: ' +
(Platform.BufferUtils.isBuffer(result.body) ? result.body.toString() : result.body)
);
}
}

export type ResourceCallback<T = unknown> = (
Expand Down Expand Up @@ -307,32 +283,15 @@ class Resource {
envelope: Utils.Format | null,
throwError: boolean
): Promise<ResourceResponse<T> | ResourceResult<T>> {
let callback: ResourceCallback<T>;

const promise = new Promise<ResourceResponse<T> | ResourceResult<T>>((resolve, reject) => {
callback = (err, body, headers, unpacked, statusCode) => {
if (throwError) {
if (err) {
reject(err);
} else {
resolve({ body, headers, unpacked, statusCode });
}
} else {
resolve({ err, body, headers, unpacked, statusCode });
}
};
});

if (Logger.shouldLog(Logger.LOG_MICRO)) {
callback = logResponseHandler(callback!, method, path, params);
}

if (envelope) {
callback = unenvelope(callback!, client._MsgPack, envelope);
(params = params || {})['envelope'] = envelope;
}

function doRequest(this: any, headers: Record<string, string>, params: Record<string, any>) {
async function doRequest(
this: any,
headers: Record<string, string>,
params: Record<string, any>
): Promise<ResourceResult<T>> {
if (Logger.shouldLog(Logger.LOG_MICRO)) {
Logger.logAction(
Logger.LOG_MICRO,
Expand Down Expand Up @@ -364,26 +323,58 @@ class Resource {
);
}

client.http.do(method, path, headers, body, params, function (err, res, headers, unpacked, statusCode) {
if (err && Auth.isTokenErr(err as ErrorInfo)) {
/* token has expired, so get a new one */
Utils.whenPromiseSettles(client.auth.authorize(null, null), function (err: ErrorInfo | null) {
if (err) {
callback(err);
return;
}
/* retry ... */
withAuthDetails(client, headers, params, callback, doRequest);
});
return;
}
callback(err as ErrorInfo, res as T | undefined, headers, unpacked, statusCode);
type HttpResult = {
error?: ErrnoException | IPartialErrorInfo | null;
body?: unknown;
headers?: RequestCallbackHeaders;
unpacked?: boolean;
statusCode?: number;
};

const httpResult = await new Promise<HttpResult>((resolve) => {
client.http.do(method, path, headers, body, params, function (error, body, headers, unpacked, statusCode) {
resolve({ error, body, headers, unpacked, statusCode });
});
});

if (httpResult.error && Auth.isTokenErr(httpResult.error as ErrorInfo)) {
/* token has expired, so get a new one */
await client.auth.authorize(null, null);
/* retry ... */
// TODO this was previously using the response headers, which seems unintentional
return withAuthDetails(client, headers, params, doRequest);
}

return {
err: httpResult.error as ErrorInfo,
body: httpResult.body as T | undefined,
headers: httpResult.headers,
unpacked: httpResult.unpacked,
statusCode: httpResult.statusCode,
};
}

let result = await withAuthDetails<T>(client, headers, params, doRequest);

if (envelope) {
result = unenvelope(result, client._MsgPack, envelope);
}

withAuthDetails(client, headers, params, callback!, doRequest);
if (Logger.shouldLog(Logger.LOG_MICRO)) {
logResult(result, method, path, params);
}

if (throwError) {
if (result.err) {
throw result.err;
} else {
const response: Omit<ResourceResult<T>, 'err'> & Pick<Partial<ResourceResult<T>>, 'err'> = { ...result };
delete response.err;
return response;
}
}

return promise;
return result;
}
}

Expand Down

0 comments on commit 8a912d2

Please sign in to comment.