Skip to content

Commit

Permalink
fix(amplify-appsync-simulator): bail out early on exceptions (#6663)
Browse files Browse the repository at this point in the history
This commit detects templates that use $util.error and stops
execution from propagating further.
  • Loading branch information
cjihrig committed Mar 1, 2021
1 parent f12c994 commit 59d66fe
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,27 @@ describe('Pipeline Resolvers', () => {
expect(result).toEqual('RESPONSE_TEMPLATE_RESULT');
expect(context.appsyncErrors).toEqual(['REQUEST_TEMPLATE_ERROR', 'RESPONSE_TEMPLATE_ERROR']);
});

it('bails out early on terminal error', async () => {
resolver = new AppSyncPipelineResolver(baseConfig, simulatorContext);
fnImpl.fn1 = {
resolve: jest.fn().mockImplementation((source, args, stash, prevResult, context, info) => {
const err = new Error('fn1-ERROR');
context.appsyncErrors = [...context.appsyncErrors, err];
return {
result: err.message,
stash: { ...stash, exeSeq: [...(stash.exeSeq || []), 'fn1'] },
hadException: true,
};
}),
};

const source = 'SOURCE';
const context = {
appsyncErrors: [],
};
const result = await resolver.resolve(source, {}, context, {});
expect(result).toEqual('fn1-ERROR');
});
});
});
8 changes: 5 additions & 3 deletions packages/amplify-appsync-simulator/src/resolvers/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class AmplifySimulatorFunction extends AppSyncBaseResolver {
}
}

async resolve(source, args, stash, prevResult, context, info): Promise<{ result: any; stash: any }> {
async resolve(source, args, stash, prevResult, context, info): Promise<{ result: any; stash: any; hadException: boolean }> {
let result = null;
let error = null;
const requestMappingTemplate = this.getRequestMappingTemplate();
Expand All @@ -27,11 +27,12 @@ export class AmplifySimulatorFunction extends AppSyncBaseResolver {
const requestTemplateResult = await requestMappingTemplate.render({ source, arguments: args, stash, prevResult }, context, info);
context.appsyncErrors = [...context.appsyncErrors, ...requestTemplateResult.errors];

if (requestTemplateResult.isReturn) {
// #return was used in template, bail and don't run data invoker
if (requestTemplateResult.isReturn || requestTemplateResult.hadException) {
// #return was used in template, or an exception occurred. Bail and don't run data invoker.
return {
result: requestTemplateResult.result,
stash: requestTemplateResult.stash,
hadException: requestTemplateResult.hadException,
};
}
try {
Expand All @@ -51,6 +52,7 @@ export class AmplifySimulatorFunction extends AppSyncBaseResolver {
return {
stash: responseMappingResult.stash,
result: responseMappingResult.result,
hadException: responseMappingResult.hadException,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,31 @@ export class AppSyncPipelineResolver extends AppSyncBaseResolver {
let stash = {};
let templateErrors;
let isReturn;
let hadException: boolean;

// Pipeline request mapping template
({ result, stash, errors: templateErrors, isReturn } = requestMappingTemplate.render(
({ result, stash, errors: templateErrors, isReturn, hadException } = requestMappingTemplate.render(
{ source, arguments: args, stash },
context,
info,
));

context.appsyncErrors = [...context.appsyncErrors, ...(templateErrors || [])];

if (isReturn) {
//Request mapping template called #return, don't process further
if (isReturn || hadException) {
//Request mapping template called #return or an exception occurred, don't process further
return result;
}

let prevResult = result;
for (let fnName of this.config.functions) {
const fnResolver = this.simulatorContext.getFunction(fnName);
({ result: prevResult, stash } = await fnResolver.resolve(source, args, stash, prevResult, context, info));
({ result: prevResult, stash, hadException } = await fnResolver.resolve(source, args, stash, prevResult, context, info));

// If an exception occurred, do not continue processing.
if (hadException) {
return prevResult;
}
}

// pipeline response mapping template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ export class AppSyncUnitResolver extends AppSyncBaseResolver {
const requestMappingTemplate = this.getRequestMappingTemplate();
const responseMappingTemplate = this.getResponseMappingTemplate();
const dataLoader = this.simulatorContext.getDataLoader(this.config.dataSourceName);
const { result: requestPayload, errors: requestTemplateErrors, isReturn } = requestMappingTemplate.render(
const { result: requestPayload, errors: requestTemplateErrors, isReturn, hadException } = requestMappingTemplate.render(
{ source, arguments: args },
context,
info,
);
context.appsyncErrors = [...context.appsyncErrors, ...requestTemplateErrors];
let result = null;
let error;
if (isReturn) {
// template has #return bail and return the value specified in the template
if (isReturn || hadException) {
// Template has #return, or an exception occurred. Bail and return the value specified in the template.
return requestPayload;
}
try {
Expand Down
16 changes: 11 additions & 5 deletions packages/amplify-appsync-simulator/src/velocity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,33 @@ export class VelocityTemplate {
ctxValues: AppSyncVTLRenderContext,
requestContext: AppSyncGraphQLExecutionContext,
info?: GraphQLResolveInfo,
): { result; stash; errors; isReturn: boolean } {
): { result; stash; errors; isReturn: boolean; hadException: boolean } {
const context = this.buildRenderContext(ctxValues, requestContext, info);
let templateResult;
try {
templateResult = this.compiler.render(context);
} catch (e) {
const lastError = context.util.errors.length && context.util.errors[context.util.errors.length - 1];
if (lastError && lastError instanceof ValidateError) {
return { result: lastError.data, errors: [...context.util.errors], isReturn: true, stash: context.ctx.stash.toJSON() };
return {
result: lastError.data,
errors: [...context.util.errors],
isReturn: true,
stash: context.ctx.stash.toJSON(),
hadException: true,
};
}
return { result: null, errors: [...context.util.errors], isReturn: false, stash: context.ctx.stash.toJSON() };
return { result: null, errors: [...context.util.errors], isReturn: false, stash: context.ctx.stash.toJSON(), hadException: true };
}
const isReturn = this.compiler._state.return; // If the template has #return, then set the value
const stash = context.ctx.stash.toJSON();
try {
const result = JSON.parse(templateResult);
return { result, stash, errors: context.util.errors, isReturn };
return { result, stash, errors: context.util.errors, isReturn, hadException: false };
} catch (e) {
if (isReturn) {
// # when template has #return, if the value is non JSON, we pass that along
return { result: templateResult, stash, errors: context.util.errors, isReturn };
return { result: templateResult, stash, errors: context.util.errors, isReturn, hadException: false };
}
const errorMessage = `Unable to convert ${templateResult} to class com.amazonaws.deepdish.transform.model.lambda.LambdaVersionedConfig.`;
throw new TemplateSentError(errorMessage, 'MappingTemplate', null, null, info);
Expand Down

0 comments on commit 59d66fe

Please sign in to comment.