Skip to content

Commit

Permalink
Enable stricter TS checks, fix validation issues (#31)
Browse files Browse the repository at this point in the history
* Enable more strict TS checks and fix validation errors

* Resolve small TODO
  • Loading branch information
Siegrift authored Sep 21, 2023
1 parent c9a78d0 commit 805f27d
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 39 deletions.
8 changes: 4 additions & 4 deletions packages/api/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ export const batchUpsertData = async (request: ApiRequest): Promise<ApiResponse>
};

export const getData = async (request: ApiRequest): Promise<ApiResponse> => {
if (isNil(request.queryParams.airnode))
return generateErrorResponse(400, 'Invalid request, path parameter airnode address is missing');
const airnode = request.queryParams.airnode;
if (isNil(airnode)) return generateErrorResponse(400, 'Invalid request, path parameter airnode address is missing');

const goValidateSchema = await go(() => evmAddressSchema.parseAsync(request.queryParams.airnode));
const goValidateSchema = await go(() => evmAddressSchema.parseAsync(airnode));
if (!goValidateSchema.success)
return generateErrorResponse(400, 'Invalid request, path parameter must be an EVM address');

const goReadDb = await go(() => getAllBy(request.queryParams.airnode));
const goReadDb = await go(() => getAllBy(airnode));
if (!goReadDb.success)
return generateErrorResponse(500, 'Unable to get signed data from database', goReadDb.error.message);

Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/in-memory-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const signedDataCache: Record<string /* Airnode ID */, Record<string /* Template
// The API is deliberately asynchronous to mimic a database call.
export const getBy = async (airnodeId: string, templateId: string) => {
if (!signedDataCache[airnodeId]) return null;
return signedDataCache[airnodeId][templateId] ?? null;
return signedDataCache[airnodeId]![templateId] ?? null;
};

// The API is deliberately asynchronous to mimic a database call.
Expand All @@ -19,7 +19,7 @@ export const getAll = async () => signedDataCache;
// The API is deliberately asynchronous to mimic a database call.
export const put = async (signedData: SignedData) => {
signedDataCache[signedData.airnode] = signedDataCache[signedData.airnode] ?? {};
signedDataCache[signedData.airnode][signedData.templateId] = signedData;
signedDataCache[signedData.airnode]![signedData.templateId] = signedData;
};

// The API is deliberately asynchronous to mimic a database call.
Expand Down
8 changes: 3 additions & 5 deletions packages/api/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
"skipLibCheck": true,
"composite": true,
"noFallthroughCasesInSwitch": true,

// Disabled because they are too restrictive
"exactOptionalPropertyTypes": false,
"noUncheckedIndexedAccess": false,
"noImplicitReturns": false,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,

// Disabled because they are covered by Eslint rules
"noUnusedLocals": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-pusher/src/fetch-beacon-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const fetchBeaconDataInLoop = async (signedApiUpdate: SignedApiUpdate) => {
const startTimestamp = Date.now();
const templateResponses = await makeTemplateRequests(signedApiUpdate);
const signedResponses = await signTemplateResponses(templateResponses);
signedResponses.forEach(([templateId, signedResponse]) => templateValues[templateId].put(signedResponse));
signedResponses.forEach(([templateId, signedResponse]) => templateValues[templateId]!.put(signedResponse));
const duration = Date.now() - startTimestamp;

await sleep(signedApiUpdate.fetchInterval * 1_000 - duration);
Expand Down
20 changes: 11 additions & 9 deletions packages/data-pusher/src/make-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ export const makeTemplateRequests = async (signedApiUpdate: SignedApiUpdate): Pr

// Because each beacon have same operation, just take first one as operational template
// See the function validateTriggerReferences in validation.ts
const operationTemplateId = beacons[beaconIds[0]].templateId;
const operationTemplate = templates[operationTemplateId];
const operationTemplateId = beacons[beaconIds[0]!]!.templateId;
const operationTemplate = templates[operationTemplateId]!;

const parameters = abi.decode(operationTemplate.parameters);
const endpoint = endpoints[operationTemplate.endpointId];
const endpoint = endpoints[operationTemplate.endpointId]!;

const aggregatedApiCall: node.BaseAggregatedApiCall = {
parameters,
Expand All @@ -168,20 +168,22 @@ export const makeTemplateRequests = async (signedApiUpdate: SignedApiUpdate): Pr
aggregatedApiCall,
};

const [_, apiCallResponse] = await limiter.schedule({ expiration: 90_000 }, () => callApi(operationPayload));
const [_, apiCallResponse] = await (limiter
? limiter.schedule({ expiration: 90_000 }, () => callApi(operationPayload))
: callApi(operationPayload));

if (node.api.isPerformApiCallFailure(apiCallResponse)) {
const message = `Failed to make API call for the endpoint [${endpoint.oisTitle}] ${endpoint.endpointName}.`;
logger.warn(message, { meta: { 'Operation-Template-ID': operationTemplateId } });
return [];
}

const templateIds = beaconIds.map((beaconId) => beacons[beaconId].templateId);
const templateIds = beaconIds.map((beaconId) => beacons[beaconId]!.templateId);

const templateResponsePromises = templateIds.map(async (templateId) => {
const template = templates[templateId];
const template = templates[templateId]!;
const parameters = abi.decode(template.parameters);
const endpoint = endpoints[template.endpointId];
const endpoint = endpoints[template.endpointId]!;

const aggregatedApiCall: node.BaseAggregatedApiCall = {
parameters,
Expand Down Expand Up @@ -255,8 +257,8 @@ export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) =>
const provider = signedApis.find((a) => a.name === providerName)!;

const batchPayloadOrNull = beaconIds.map((beaconId): SignedApiPayload | null => {
const { templateId, airnode } = beacons[beaconId];
const delayedSignedData = templateValues[templateId].get(updateDelay);
const { templateId, airnode } = beacons[beaconId]!;
const delayedSignedData = templateValues[templateId]!.get(updateDelay);
if (isNil(delayedSignedData)) return null;
return { airnode, templateId, beaconId, ...delayedSignedData };
});
Expand Down
6 changes: 3 additions & 3 deletions packages/data-pusher/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface State {
config: Config;
templateValues: TemplateValueStorage;
walletPrivateKey: string;
apiLimiters: Record<string, Bottleneck>;
apiLimiters: Record<string, Bottleneck | undefined>;
}

let state: State;
Expand All @@ -35,7 +35,7 @@ export const buildApiLimiters = (config: Config) => {
const directGatewayOverrides = config?.rateLimiting?.overrides?.directGateways;

if (directGatewayOverrides && directGatewayOverrides[ois.title]) {
const { minTime, maxConcurrent } = directGatewayOverrides[ois.title];
const { minTime, maxConcurrent } = directGatewayOverrides[ois.title]!;

return [
ois.title,
Expand Down Expand Up @@ -66,7 +66,7 @@ export const buildApiLimiters = (config: Config) => {
// Make use of the reference/pointer nature of objects
const apiLimiters = Object.fromEntries(
Object.entries(config.templates).map(([templateId, template]) => {
const title = endpointTitles[template.endpointId];
const title = endpointTitles[template.endpointId]!;
return [templateId, oisLimiters[title]];
})
);
Expand Down
39 changes: 31 additions & 8 deletions packages/data-pusher/src/validation/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const validateOisReferences: SuperRefinement<{ ois: OIS[]; endpoints: Endpoints
return;
}
// Take first OIS fits the filter rule, then check specific endpoint existence
const ois = oises[0];
const ois = oises[0]!;
const endpoints = ois.endpoints.filter(({ name }: oisEndpoint) => name === endpointName);

if (endpoints.length === 0) {
Expand Down Expand Up @@ -180,12 +180,35 @@ const validateTriggerReferences: SuperRefinement<{
// Check only if beaconIds contains more than 1 beacon
if (beaconIds.length > 1) {
const operationPayloadPromises = beaconIds.map((beaconId) => {
// TODO: This can throw when there is no beacon - which is an unhandled error in this validation check.
const template = templates[beacons[beaconId].templateId];
const beacon = beacons[beaconId];
if (!beacon) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Unable to find beacon with ID: ${beaconId}`,
path: ['beacons'],
});
return;
}
const template = templates[beacon.templateId];
if (!template) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Unable to find template with ID: ${beacon.templateId}`,
path: ['templates'],
});
return;
}

// TODO: This can throw when there is no template - which is an unhandled error in this validation check.
const parameters = abi.decode(template.parameters);
const endpoint = endpoints[template.endpointId];
if (!endpoint) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Unable to find endpoint with ID: ${template.endpointId}`,
path: ['endpoints'],
});
return;
}

const aggregatedApiCall = {
parameters,
Expand Down Expand Up @@ -245,10 +268,10 @@ export const rateLimitingSchema = z.object({
.optional(),
});

const validateOisRateLimiterReferences: SuperRefinement<{ ois: OIS[]; rateLimiting?: RateLimitingConfig }> = (
config,
ctx
) => {
const validateOisRateLimiterReferences: SuperRefinement<{
ois: OIS[];
rateLimiting?: RateLimitingConfig | undefined;
}> = (config, ctx) => {
const directGateways = config.rateLimiting?.overrides?.directGateways ?? {};
const oises = config?.ois ?? [];

Expand Down
5 changes: 3 additions & 2 deletions packages/data-pusher/src/validation/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export const interpolateSecrets = (config: unknown, secrets: Record<string, stri
);

if (!goInterpolated.success) {
// TODO: This should use error cause
throw new Error(`Error interpolating secrets. Make sure the secrets format is correct. ${goInterpolated.error}`);
throw new Error(`Error interpolating secrets. Make sure the secrets format is correct.`, {
cause: goInterpolated.error,
});
}

const goJson = goSync(() => JSON.parse(goInterpolated.data));
Expand Down
8 changes: 3 additions & 5 deletions packages/data-pusher/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
"skipLibCheck": true,
"composite": true,
"noFallthroughCasesInSwitch": true,

// Disabled because they are too restrictive
"exactOptionalPropertyTypes": false,
"noUncheckedIndexedAccess": false,
"noImplicitReturns": false,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,

// Disabled because they are covered by Eslint rules
"noUnusedLocals": false,
Expand Down

0 comments on commit 805f27d

Please sign in to comment.