2

I am attempting to return an object from a AWS Lambda function instead of a simple string.

// ...
    context.fail({
        "email": "Email address is too short",
        "firstname": "First name is too short"
    });
// ...

I have already used the errorMessage for mapping error responses to status codes and that has been great:

// ...
    context.fail('That "username" has already been taken.');
// ...

Am I simply trying to do something that the AWS API Gateway does not afford?

I have also already found this article which helped: Is there a way to change the http status codes returned by Amazon API Gateway?.

Community
  • 1
  • 1
kalisjoshua
  • 2,306
  • 2
  • 26
  • 37

4 Answers4

9

Update Since time of writing, lambda has updated the invocation signature and now passes event, context, callback.

Instead of calling context.done(err, res) you should use callback(err, res). Note that what was true for context.done still applies to the callback pattern.

Should also add that with API Gateways proxy and integration implementation this entire thread is pretty much obsolete. I recommend reading this article if you are integrating API Gateway with Lambda: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html

Original response below

First things first, let's clear a few things up.

context.done() vs. context.fail()/context.success

context.done(error, result); is nothing but a wrapper around context.fail(error); and context.success(response); The Lambda documentation clearly states that result is ignored if error is non null:

If the Lambda function was invoked using the RequestResponse (synchronous) invocation type, the method returns response body as follows: If the error is null, set the response body to the string representation of result. This is similar to the context.succeed(). If the error is not null, set the response body to error. If the function is called with a single argument of type error, the error value will be populated in the response body. http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html

What this means is that it won't matter whether you use a combination of fail/success or done, the behaviour is exactly the same.

API Gateway and Response Code Mapping

I have tested every thinkable combination of response handling from Lambda in combination with Response code mapping in API Gateway.

The conclusion of these tests are that the "Lambda Error RegExp" is only executed against a Lambda error, i.e: you have to call context.done(error);or context.fail(error); for the RegExp to actually trigger.

Now, this presents a problem as, has already been noted, Lambda takes your error and sticks it in an object and calls toString() on whatever you supplied:

{ errorMessage: yourError.toString() }

If you supplied an error object you'll get this:

{ errorMessage: "[object Object]" }

Not very helpful at all.

The only workaround I have found thus far is to call

context.fail(JSON.stringify(error));

and then in my client do:

var errorObject = JSON.parse(error.errorMessage);

It's not very elegant but it works. As part of my error I have a property called "code". It could look something like this:

{ 
    code: "BadRequest", 
    message: "Invalid argument: parameter name" 
}

When I stringify this object I get:

"{\"code\":\"BadRequest\",\"message\":\"Invalid argument: parameter name\"}"

Lambda will stick this string in the errorMessage property of the response and I can now safely grep for .*"BadRequest".* in the API Gateway response mapping.

It's very much a hack that works around two somewhat strange quirks of Lambda and API Gateway:

  1. Why does Lambda insist on wrapping the error instead of just giving it back as is?
  2. Why doesn't API Gateway allow us to grep in the Lambda result, only the error?

I am on my way to open a support case with Amazon regarding these two rather odd behaviours.

Carl
  • 680
  • 6
  • 22
  • Looks like currently `context.done` etc methods are either deprecated or not supported at all. They are not mentioned in the docs; instead they suppose using `callback` which is the third argument to the handler. Its signature is identical to `context.done`, i.e. it accepts `error` as the first argument and `result` as the second one. – MarSoft Jun 22 '17 at 15:43
  • @MarSoft I've updated the response to better reflect the current possibilities and AWS implementation – Carl Jun 26 '17 at 08:26
3

You don't have to use context.fail, use success but send different statusCode and an errorMessage, here is an example of how i format my output:

try {
    // Call the callable function with the defined array parameters
    // All the function called here will be catched if they throw exceptions
    result.data = callable_function.apply(this, params);
    result.statusCode = 200;
    result.operation = operation;
    result.errorMessage = ""
} catch (e) {
    result.data = [];
    result.statusCode = 500;
    result.errorMessage = e.toString();
    result.method = method;
    result.resource = resource;
}

// If everything went smooth, send back the result
// If context succeed is not called AWS Lambda will fire the function
// again because it is not successfully exited
context.succeed(result);

Use the consumer logic to handle different errors case logic, don't forget that you pay for the time your function is running...

e-nouri
  • 2,576
  • 1
  • 21
  • 36
  • When I try this, with no Lambda Regex Error pattern, on 200 and 404 the response code of 404 is never returned when I test it. I get the correct data coming through with the "statusCode" set to 404 but the actual response code is 200. Am I missing something? – kalisjoshua Oct 07 '15 at 12:06
  • Yeah you are, in integration response don't map your response again, just send the lambda output through, and use the default response mapping. – e-nouri Oct 07 '15 at 12:56
  • I've just removed all response codes from both "Method Response" and "Integration Response" and when I test, setting `statusCode` to 200 or 400, I only get 500. When I add in 200 and 400 in "Method Response" there is no change. When I add in 200 and 400 in "Integration Response" In only get 200 even if I set `statusCode` to 400. When I remove 200 and 400 from "Method Response" then all I get is 500. Thank you for your help btw. – kalisjoshua Oct 07 '15 at 13:10
  • Np, In "Integration Request" get the data needed for your lambda to run, you will have it in the event, then send the status code you want in your lambda, in the "Integration Response" you just send the output through, you don't map it, use "context.succeed(result)" with the result having your result.statusCode you want. Then in your app consume the status code. You can then add other mapping for the status code if you like to have the proper HTTP rest codes sent back. – e-nouri Oct 07 '15 at 14:36
  • So, what you are saying is that when you use context.succeed(), you can not map the actual http status code to anything other than the default. This means that you only need one integration response. Correct? – quintonm May 05 '16 at 21:45
2

You should replace the use of your context.fail with context.done and use context.fail only for very serious Lambda function failures since it doesn't allow more than one output parameter. Integration Response is able to match mapping template by performing regex on the first parameter passed to context.done this also maps HTTP status code to the response. You can't pass this response status code directly from Lambda since it's the role of API Gateway Integration Response to abstract the HTTP protocol.

See the following:

context.done('Not Found:', <some object you can use in the model>);

and the Integration Response panel this setting:

API Gateway Integration Response Error mapping

You can replicate similar approach for any kind of error. You should also create and map the error model to your response.

adamkonrad
  • 6,794
  • 1
  • 34
  • 41
  • I have been able to get this working but I haven't been able to return a 400 with a body; the body contains the validation errors. But I didn't try the third argument to `context.fail()`. – kalisjoshua Oct 07 '15 at 12:08
  • You need to add appropriate model for that response code and use the model to return data. – adamkonrad Oct 07 '15 at 12:27
  • Do have an example? I spent all day yesterday trying to figure this out and haven't been able to find anything that helps explain how it should "work". All I see in the log is `{"errorMessage": ""}` the additional arguments to `context.fail()` are obviously available. – kalisjoshua Oct 07 '15 at 12:43
  • No problem. This [Lambda programming model](http://docs.aws.amazon.com/lambda/latest/dg/programming-model.html) page contains more information on what can be done with `context.fail(...)` and it also covers how it passes data around. I think the proper way to handle what you're looking for is not calling `context.fail` and use `context.done` instead since it has more parameters and let the `API Gateway` layer handle it based on returned strings that you can regex on. – adamkonrad Oct 07 '15 at 16:25
  • I'll expand my answer. – adamkonrad Oct 08 '15 at 17:54
  • Interesting, is that second argument "" available in the context of the mapping? When I was working on it, it seemed only the first argument was available. – kalisjoshua Oct 09 '15 at 17:09
  • See the [API Gateway programming guide](http://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-dg.pdf) middle of page 9. Use `context.done` for returning payloads. `context.fail` is really just for full-on Lambda failure. – adamkonrad Oct 09 '15 at 18:09
  • I still don't see how to return the "errors object" (the second argument to `context.done()`) in the actual response. The error string is easy to match but the response body of the API call is `{"errorMessage": "error string"}` where "error string" is the first argument to `context.done()`. The second argument doesn't automatically get passed to the requestor. – kalisjoshua Oct 12 '15 at 20:21
  • You need to use the object from the second argument in your model that you map to the response in the integration request panel. Do you follow? – adamkonrad Oct 12 '15 at 21:19
  • I'll try to expand my original answer with that, I'll see what I can do. – adamkonrad Oct 12 '15 at 21:23
  • I think that the second argument is "ignored" if the first argument is NOT null; so therefore the second argument is not available in the mapping template. Side note: I think one of the problems that *Amazon has created with this is that there is no way to "share" examples or solutions because everything is configuration via their UI and not as a configuration file.* Anyway if you have a code sample of accessing the second argument in the mapping template that would be extremely helpful. – kalisjoshua Oct 13 '15 at 12:15
  • Second argument is definitely not ignored. Once they have CloudFormation support for creating API Gateway resources and setting up all the Lambda functionality, it's going to become much easier. – adamkonrad Oct 13 '15 at 12:16
  • I replaced the original image in this post with better one showing the actual `Integration Response` mapping. – adamkonrad Oct 13 '15 at 14:17
  • Thank you for the updated image but that isn't going to work because isn't "$input" the input to the Lambda and not the output from the Lambda? – kalisjoshua Oct 15 '15 at 17:01
  • No. `$input` is the input of the `Integration Request/Response`. This is verified working. – adamkonrad Oct 15 '15 at 17:55
  • When I do exactly as is described in this answer, the response body (payload) I get from the request is `{"error": "{"errorMessage":"Not Found:"}"}`. This means that the error object (second object) - `{"text": "can has errors"}` - is not passed as part of the `$input`. I really am not trying to drag this out or be difficult - I truly want to find the actual answer to this - but I am not seeing what is being suggested as a solution. – kalisjoshua Oct 22 '15 at 18:29
-1

For those who tried everything put on this question and couldn't make this work (like me), check the thedevkit comment on this post (saved my day):

https://forums.aws.amazon.com/thread.jspa?threadID=192918

Reproducing it entirely below:

I've had issues with this myself, and I believe that the newline characters are the culprit.

foo.* will match occurrences of "foo" followed by any characters EXCEPT newline. Typically this is solved by adding the '/s' flag, i.e. "foo.*/s", but the Lambda error regex doesn't seem to respect this.

As an alternative you can use something like: foo(.|\n)*

Carlos Ballock
  • 145
  • 1
  • 3