diff --git a/__tests__/__snapshots__/resolvers.test.js.snap b/__tests__/__snapshots__/resolvers.test.js.snap index 3b6fe2e..d7d35b3 100644 --- a/__tests__/__snapshots__/resolvers.test.js.snap +++ b/__tests__/__snapshots__/resolvers.test.js.snap @@ -38,6 +38,18 @@ exports[`dynamodb resolvers something 2`] = ` ] `; +exports[`error handling error 1`] = ` +{ + "message": "foo", +} +`; + +exports[`error handling unauthorized 1`] = ` +{ + "message": "Unauthorized", +} +`; + exports[`rds resolvers attributeExists nested or 1`] = ` { "statements": [ diff --git a/__tests__/helpers.js b/__tests__/helpers.js index d28e483..d336964 100644 --- a/__tests__/helpers.js +++ b/__tests__/helpers.js @@ -27,6 +27,9 @@ const runResolverFunctionOnAWS = async (code, context, functionName) => { }); const result = await client.send(command); + if (result.error) { + return result.error; + } try { return JSON.parse(result.evaluationResult); } catch (e) { @@ -72,7 +75,14 @@ export const checkResolverValid = async (code, context, functionName) => { const fn = module[functionName]; transformContextForAppSync(context); - result = fn(context); + try { + result = fn(context); + } catch (e) { + if (e.name !== "AppSyncUserError") { + throw e; + } + result = {"message": e.message} + } } expect(result).toMatchSnapshot(); }; diff --git a/__tests__/resolvers.test.js b/__tests__/resolvers.test.js index 59d6660..ee5e725 100644 --- a/__tests__/resolvers.test.js +++ b/__tests__/resolvers.test.js @@ -1016,3 +1016,23 @@ describe("rds resolvers", () => { }); }); +describe("error handling", () => { + test("error", async () => { + const code = ` + export function request(ctx) { + util.error("foo") + } + export function response(ctx) {} + `; + await checkResolverValid(code, {}, "request"); + }) + test("unauthorized", async () => { + const code = ` + export function request(ctx) { + util.unauthorized() + } + export function response(ctx) {} + `; + await checkResolverValid(code, {}, "request"); + }) +}) \ No newline at end of file diff --git a/index.js b/index.js index 1ee0041..82fdb6e 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,16 @@ import { v4 as uuidv4 } from 'uuid'; import { toJsonObject } from './rds/index.js' +class AppSyncUserError extends Error { + constructor(message, errorType, data, errorInfo) { + super(message); + this.name = "AppSyncUserError"; + this.errorType = errorType; + this.data = data; + this.errorInfo = errorInfo; + } +} + export const dynamodbUtils = { toDynamoDB: function(value) { if (typeof (value) === "number") { @@ -121,8 +131,20 @@ export const util = { return uuidv4(); }, appendError: function(message, errorType, data, errorInfo) { - // This will be handled in LocalStack in a side channel by printing to stderr - console.error({ message, errorType, data, errorInfo }); + const error = { message, errorType, data, errorInfo } + if( console.appendError ) { + // LocalStack is adding `appendError` to console, allowing to push errors to `context.outErrors` + console.appendError(error) + } else { + // To avoid breaking code where `appendError` is not implemented, we instead print to stderr + console.error({ message, errorType, data, errorInfo }); + } + }, + error: function(message, errorType, data, errorInfo) { + throw new AppSyncUserError(message, errorType, data, errorInfo) + }, + unauthorized: function() { + throw new AppSyncUserError("Unauthorized", "UnauthorizedException") }, time: { nowFormatted: function(pattern) {