Repository: serverless/serverless (JavaScript)
Date: 2019-11-12 02:52:09+00:00
Task type: Bug Fix

Issue:
AWS API Gateway request body validation not working with multiple schemas
<!--
1. If you have a question and not a bug report please ask first at http://forum.serverless.com
2. Please check if an issue already exists. This bug may have already been documented
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Bug Report

## Description

* What went wrong?
I added a Lambda function with multiple request schemas to my serverless.yml. (As described in #5956.) However, in the Cloudformation JSON created, only the last request schema gets added to the RequestModels in the `AWS::ApiGateway::Method`. It did correctly create `AWS::ApiGateway::Model`s for all request schemas.
* What did you expect should have happened?
I expected all request schemas to be added to the RequestModels in the `AWS::ApiGateway::Method`.
* What was the config you used?
Nothing special, I just ran `serverless package`.
* What stacktrace or error message from your provider did you see?
None, there are no errors.

Similar or dependent issues:
* Found this in the changes made in #5956.

`serverless.yml`:
```yml
service:
  name: request-schema-test

provider:
  name: aws
  runtime: nodejs8.10

functions:
  hello:
    handler: handler.hello
    events:
    - http:
        path: users/create
        method: post
        request:
          schema:
            "application/json": ${file(create_request.json)}
            "text/html": { foo: 'bar' }

```

`create_request.json`:
```json
{
    "definitions": {},
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "title": "The Root Schema",
    "required": [
        "username"
    ],
    "properties": {
        "username": {
            "type": "string",
            "title": "The Foo Schema",
            "default": "",
            "pattern": "^[a-zA-Z0-9]+$"
        }
    }
}
```

`cloudformation-template-update-stack.json`:
```json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application",
  "Resources": {
    "ApiGatewayMethodUsersCreatePostApplicationJsonModel": {
      "Type": "AWS::ApiGateway::Model",
      "Properties": {
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "ContentType": "application/json",
        "Schema": {
          "definitions": {},
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "title": "The Root Schema",
          "required": [
            "username"
          ],
          "properties": {
            "username": {
              "type": "string",
              "title": "The Foo Schema",
              "default": "",
              "pattern": "^[a-zA-Z0-9]+$"
            }
          }
        }
      }
    },
    "ApiGatewayMethodUsersCreatePostValidator": {
      "Type": "AWS::ApiGateway::RequestValidator",
      "Properties": {
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "ValidateRequestBody": true,
        "ValidateRequestParameters": true
      }
    },
    "ApiGatewayMethodUsersCreatePostTextHtmlModel": {
      "Type": "AWS::ApiGateway::Model",
      "Properties": {
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "ContentType": "text/html",
        "Schema": {
          "foo": "bar"
        }
      }
    },
    "ApiGatewayMethodUsersCreatePost": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "HttpMethod": "POST",
        "RequestParameters": {},
        "ResourceId": {
          "Ref": "ApiGatewayResourceUsersCreate"
        },
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "ApiKeyRequired": false,
        "AuthorizationType": "NONE",
        "Integration": {
          "IntegrationHttpMethod": "POST",
          "Type": "AWS_PROXY",
          "Uri": {
            "Fn::Join": [
              "",
              [
                "arn:",
                {
                  "Ref": "AWS::Partition"
                },
                ":apigateway:",
                {
                  "Ref": "AWS::Region"
                },
                ":lambda:path/2015-03-31/functions/",
                {
                  "Fn::GetAtt": [
                    "HelloLambdaFunction",
                    "Arn"
                  ]
                },
                "/invocations"
              ]
            ]
          }
        },
        "MethodResponses": [],
        "RequestValidatorId": {
          "Ref": "ApiGatewayMethodUsersCreatePostValidator"
        },
        "RequestModels": {
          "text/html": {
            "Ref": "ApiGatewayMethodUsersCreatePostTextHtmlModel"
          }
        }
      }
    }
  }
}
```
## Additional Data

* ***Serverless Framework Version you're using***:
1.43.0 (latest).
* ***Operating System***:
Windows 10 Pro.
* ***Stack Trace***:
* ***Provider Error messages***:

## Files:
It didn't allow YAML and JSON so they're all TXT-files, sorry.
[serverless.txt](https://github.com/serverless/serverless/files/3203036/serverless.txt)
[create_request.txt](https://github.com/serverless/serverless/files/3203045/create_request.txt)
[cloudformation-template-update-stack.txt](https://github.com/serverless/serverless/files/3203049/cloudformation-template-update-stack.txt) (I removed all other Resources and Outputs.)




Issue discussion:
+1
This line overwrites the map of models for each schema, rather than adding to it:
https://github.com/serverless/serverless/blob/f93b27bf684d9a14b1e67ec554a7719ca3628135/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js#L88



Solution patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js
index e846393a251..d83ad2ba82e 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js
@@ -85,7 +85,8 @@ module.exports = {
           );
 
           template.Properties.RequestValidatorId = { Ref: validatorLogicalId };
-          template.Properties.RequestModels = { [contentType]: { Ref: modelLogicalId } };
+          template.Properties.RequestModels = template.Properties.RequestModels || {};
+          template.Properties.RequestModels[contentType] = { Ref: modelLogicalId };
 
           _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
             [modelLogicalId]: {


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 cdfe6ba9452..a4481719073 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
@@ -105,6 +105,55 @@ describe('#compileMethods()', () => {
     });
   });
 
+  it('should support multiple request schemas', () => {
+    awsCompileApigEvents.validated.events = [
+      {
+        functionName: 'First',
+        http: {
+          path: 'users/create',
+          method: 'post',
+          integration: 'AWS',
+          request: { schema: { 'application/json': { foo: 'bar' }, 'text/plain': 'foo' } },
+        },
+      },
+    ];
+    return awsCompileApigEvents.compileMethods().then(() => {
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .ApiGatewayMethodUsersCreatePostValidator
+      ).to.deep.equal({
+        Type: 'AWS::ApiGateway::RequestValidator',
+        Properties: {
+          RestApiId: { Ref: 'ApiGatewayRestApi' },
+          ValidateRequestBody: true,
+          ValidateRequestParameters: true,
+        },
+      });
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .ApiGatewayMethodUsersCreatePostApplicationJsonModel
+      ).to.deep.equal({
+        Type: 'AWS::ApiGateway::Model',
+        Properties: {
+          RestApiId: { Ref: 'ApiGatewayRestApi' },
+          ContentType: 'application/json',
+          Schema: { foo: 'bar' },
+        },
+      });
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .ApiGatewayMethodUsersCreatePost.Properties.RequestModels
+      ).to.deep.equal({
+        'application/json': { Ref: 'ApiGatewayMethodUsersCreatePostApplicationJsonModel' },
+        'text/plain': { Ref: 'ApiGatewayMethodUsersCreatePostTextPlainModel' },
+      });
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .ApiGatewayMethodUsersCreatePost.Properties.RequestValidatorId
+      ).to.deep.equal({ Ref: 'ApiGatewayMethodUsersCreatePostValidator' });
+    });
+  });
+
   it('should have request parameters defined when they are set', () => {
     awsCompileApigEvents.validated.events = [
       {