Repository: serverless/serverless (JavaScript)
Date: 2020-05-21 09:53:07+00:00
Task type: Feature

Issue:
Enable setting contentHandling property at the integration response level
### Use case description

On my project, I need to convert a lambda response to binary in API Gateway, which can be done by using lambda integration, returning a b64 encoded string and setting the response contentHandling property to CONVERT_TO_BINARY.

However, I want to convert the returned value only when the lambda's execution is successful. When it errored, I want to pass the body through so I can read it. It means having different contentHandling properties for different integration response.

CloudFormation enables setting the contentHandling property on a per integration response basis. However, this is not implemented in Serverless yet.

### Proposed solution

```
functions:
  test:
    handler: test.handler
    events:
      - http:
          method: get
          path: api/test
          integration: lambda
          response:
            statusCodes:
              200:
                pattern: ''
                contentHandling: CONVERT_TO_BINARY
              400:
                pattern: '400'
                contentHandling: CONVERT_TO_TEXT
```

=> The content handling property is correctly set for each integration response.


Issue discussion:
@ThomasAribart Thanks for request, it's definitely worth to fix it.

Your proposal looks good, still wonder whether `response.contentHandling` should be applied only in case of `200`. I'd say it's a bug we apply to all status codes. What do you think?



@medikoo Thanks 👍

I agree with your suggestion. So it would mean something like:
```
ContentHandling: config.contentHandling || statusCode === 200 ? http.response.contentHandling : undefined
```

Wouldn't it be a breaking change, though ?
> So it would mean something like:

I think it should be in parenthesis as:

```javascript
config.contentHandling || (statusCode === 200 ? http.response.contentHandling : undefined)
```

> Wouldn't it be a breaking change, though ?

I assume it was used purely for binary cases, and that nobody intentionally wanted to serve binary response for non 200 status code.

If you feel that it can be breaking, can you elaborate on case?

If someone intentionally set the contentHandling parameter to CONVERT_TO_TEXT or CONVERT_TO_BINARY for status codes other than 200, that may cause a regression. I agree it seems a bit strange, but theoretically, it can happen.

Besides, maybe we should consider checking all 2XX status codes ?
> I agree it seems a bit strange, but theoretically, it can happen.

Yes, I know that theoretically this could be setup, still I wonder is there a valid use case (whether we'd really break for someone if we change that behavior).

I want to investigate that, as it appears that source of a problem is that we apply this handling to any status code unconditionally - that looks as design issue, that might be worth fixing in first place.

> Besides, maybe we should consider checking all 2XX status codes ?

We probably can do that. Still e.g. it doesn't apply to 204 or 205, but I guess there's no harm in passing it for it (I believe AWS won't fail if it won't receive any content to transform)

@medikoo Alright, I'll rework the PR in that direction (applying the contentHandling only to 2XXs) 👌

Solution patch:
diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md
index f46ddcbdb0b..2fd08521382 100644
--- a/docs/providers/aws/events/apigateway.md
+++ b/docs/providers/aws/events/apigateway.md
@@ -1567,7 +1567,7 @@ provider:
 
 In your Lambda function you need to ensure that the correct `content-type` header is set. Furthermore you might want to return the response body in base64 format.
 
-To convert the request or response payload, you can set the [contentHandling](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-workflow.html) property.
+To convert the request or response payload, you can set the [contentHandling](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-workflow.html) property (if set, the response contentHandling property will be passed to integration responses with 2XXs method response statuses).
 
 ```yml
 functions:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
index 16333163368..e7b16fd4342 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
@@ -161,7 +161,7 @@ module.exports = {
           SelectionPattern: config.pattern || '',
           ResponseParameters: responseParameters,
           ResponseTemplates: {},
-          ContentHandling: http.response.contentHandling,
+          ContentHandling: statusCode.startsWith('2') ? http.response.contentHandling : undefined,
         };
 
         if (config.headers) {


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
index b26f9476881..0352590a80a 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
@@ -1205,7 +1205,7 @@ describe('#compileMethods()', () => {
       });
     });
 
-    it('should use defined content-handling behavior', () => {
+    it('should use defined content-handling behavior (request)', () => {
       awsCompileApigEvents.validated.events = [
         {
           functionName: 'First',
@@ -1234,6 +1234,42 @@ describe('#compileMethods()', () => {
       });
     });
 
+    it('should use defined response content-handling behavior for 2XX only (response)', () => {
+      awsCompileApigEvents.validated.events = [
+        {
+          functionName: 'First',
+          http: {
+            method: 'GET',
+            path: 'users/list',
+            integration: 'AWS',
+            response: {
+              contentHandling: 'CONVERT_TO_BINARY',
+              statusCodes: {
+                200: {
+                  pattern: '',
+                },
+                400: {
+                  pattern: '400',
+                },
+              },
+            },
+          },
+        },
+      ];
+      return awsCompileApigEvents.compileMethods().then(() => {
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+            .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0]
+            .ContentHandling
+        ).to.equal('CONVERT_TO_BINARY');
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+            .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1]
+            .ContentHandling
+        ).to.equal(undefined);
+      });
+    });
+
     it('should set custom request templates', () => {
       awsCompileApigEvents.validated.events = [
         {