Skip to content

Commit

Permalink
Revive dates
Browse files Browse the repository at this point in the history
  • Loading branch information
asein-sinch committed Feb 13, 2024
1 parent d2772e1 commit 0760eae
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 11 deletions.
31 changes: 31 additions & 0 deletions packages/sdk-client/src/client/api-client-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,34 @@ export async function invalidateAndRegenerateJwt(
throw new GenericError(errorMessage, errorContext);
}
}

/**
* Go through all an object's properties and transform to date the values that match the right format
* @param {any} input - the response object after all the response plugins have been run
* @return {any} - the response where the values matching a date are revived as Date objects
*/
export const reviveDates = (input: any): any => {
if (Array.isArray(input)) {
// Process array elements recursively
return input.map((item) => reviveDates(item));
} else if (typeof input === 'object' && input !== null) {
// Process object properties recursively
const newObj: any = {};
for (const key in input) {
if (Object.prototype.hasOwnProperty.call(input, key)) {
newObj[key] = reviveDates(input[key]);
}
}
return newObj;
} else if (isDateString(input)) {
// Convert string date to Date object
return new Date(input);
} else {
// Return other types as-is
return input;
}
};

const isDateString = (value: any): boolean => {
return typeof value === 'string' && !isNaN(Date.parse(value));
};
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,13 @@ const updateQueryParamsAndSendRequest = <T>(
...requestOptions.queryParams,
...newParams,
};
const extractedParams = apiClient.extractQueryParams(newQueryParams, Object.keys(newQueryParams));
const newRequestOptions: RequestOptions = {
...requestOptions,
queryParams: extractedParams,
queryParams: newQueryParams,
};
const newUrl = apiClient.prepareUrl(
requestOptions.basePath,
extractedParams,
newQueryParams,
);
return apiClient.processCallWithPagination<T>({
url: newUrl,
Expand Down
50 changes: 42 additions & 8 deletions packages/sdk-client/src/client/api-fetch-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
ResponseJSONParseError, ApiCallParametersWithPagination, PageResult,
} from '../api';
import fetch, { Response, Headers } from 'node-fetch';
import { buildErrorContext, manageExpiredToken } from './api-client-helpers';
import { buildErrorContext, manageExpiredToken, reviveDates } from './api-client-helpers';
import {
buildPaginationContext,
calculateNextPage,
Expand Down Expand Up @@ -94,8 +94,8 @@ export class ApiFetchClient extends ApiClient {
throw exception;
}

// If everything went fine, we return the transformed API response
return transformedResponse;
// If everything went fine, we apply a last transformation to revive the dates, and we return the transformed API response
return reviveDates(transformedResponse);
}

private async sinchFetch(
Expand Down Expand Up @@ -125,13 +125,15 @@ export class ApiFetchClient extends ApiClient {
const errorContext: ErrorContext = buildErrorContext(props, origin);

// Execute call
return this.sinchFetchWithPagination<T>(props, errorContext);
return this.sinchFetchWithPagination<T>(props, errorContext, origin);
};

private async sinchFetchWithPagination<T>(
apiCallParameters: ApiCallParametersWithPagination,
errorContext: ErrorContext,
origin: string | null,
): Promise<PageResult<T>> {
let exception: Error | undefined;
const response = await fetch(apiCallParameters.url, apiCallParameters.requestOptions);
if (
response.status === 401
Expand All @@ -146,14 +148,46 @@ export class ApiFetchClient extends ApiClient {
}
// When handling pagination, we won't return the raw response but a PageResult
const body = await response.text();
const result = JSON.parse(body);
let result;
try {
// Try to parse the body if there is one
result = body ? JSON.parse(body) : undefined;
} catch (error: any) {
exception = new ResponseJSONParseError(
error.message || 'Fail to parse response body',
(response && response.status) || 0,
errorContext,
body,
);
}

// Load and invoke the response plugins to transform the response
const responsePlugins = this.loadResponsePlugins(
this.apiClientOptions.responsePlugins,
apiCallParameters,
response,
exception,
origin);
let transformedResponse = result;
for (const pluginRunner of responsePlugins) {
transformedResponse = await pluginRunner.transform(transformedResponse);
}

// Revive Date objects
transformedResponse = reviveDates(transformedResponse);

This comment has been minimized.

Copy link
@JPPortier

JPPortier Feb 14, 2024

What about string date representation without timezone ?
is there conflicts between general usage/parsing here and dedicated case where received date do not contains it (the timezone) and we need to fix value before transformation to Date ?

This comment has been minimized.

Copy link
@asein-sinch

asein-sinch Feb 14, 2024

Author Collaborator

This is handled in the TimezoneResponse plugin invoked just above. First the timezone are fixed and then the dates are parsed. I realized that the second part was missing 🙁


// If there has been an error at some point in the process, throw it
if (exception) {
throw exception;
}

// Read the elements' array with its key
const responseData: Array<T> = result[apiCallParameters.dataKey];
const responseData: Array<T> = transformedResponse[apiCallParameters.dataKey];
// Build the PageResult object
const nextPage = calculateNextPage(result, buildPaginationContext(apiCallParameters));
const nextPage = JSON.stringify(calculateNextPage(transformedResponse, buildPaginationContext(apiCallParameters)));
return {
data: responseData || [],
hasNextPage: hasMore(result, buildPaginationContext(apiCallParameters)),
hasNextPage: hasMore(transformedResponse, buildPaginationContext(apiCallParameters)),
nextPageValue: nextPage,
nextPage: () => createNextPageMethod<T>(
this, buildPaginationContext(apiCallParameters), apiCallParameters.requestOptions, nextPage),
Expand Down
47 changes: 47 additions & 0 deletions packages/sdk-client/tests/client/api-client-helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { reviveDates } from '../../src/client/api-client-helpers';

describe('API client helpers', () => {

it('should revive Dates', () => {
const obj = {
date1: '2024-01-31T12:00:00.000Z',
nested: {
date2: '2024-02-15T18:30:00.000Z',
},
array: [
{
date3: '2024-02-03T04:15:00.000Z',
},
{
date3: '2024-02-04T20:22:00.123Z',
},
],
otherProp: 'otherValue',
otherNumber: 0,
otherBoolean: true,
otherUndefined: undefined,
otherNull: null,
};
const expected = {
date1: new Date('2024-01-31T12:00:00.000Z'),
nested: {
date2: new Date('2024-02-15T18:30:00.000Z'),
},
array: [
{
date3: new Date('2024-02-03T04:15:00.000Z'),
},
{
date3: new Date('2024-02-04T20:22:00.123Z'),
},
],
otherProp: 'otherValue',
otherNumber: 0,
otherBoolean: true,
otherUndefined: undefined,
otherNull: null,
};
expect(reviveDates(obj)).toStrictEqual(expected);
});

});

0 comments on commit 0760eae

Please sign in to comment.