Search code examples
amazon-web-servicesaws-lambdaaws-serverlesssam

How do I set up a model to use for validation in a lambda function in a SAM template?


I am trying to set up an AWS API Gateway with SAM templates. I want to use defined models to validate the incoming event data before inserting it into a database, which I have done through the interface, but I need to be able to do it through code. Unfortunately, I am getting a variety of errors on deploying code to AWS, and running it locally does not validate the incoming data. Here is my template.yaml file:

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  company

Resources:
  ApiGatewayApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Models:
        company:
          $schema: 'http://json-schema.org/draft-04/schema#'
          type: object
          properties: 
            name:
              type: string
            email:
              type: string
            website:
              type: string
            phone:
              type: string
          required:
            - name
            - phone

  PostCompanyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/functions
      Handler: company.postCompany
      Runtime: nodejs14.x
      Architectures:
        - x86_64
      Events:
        Company:
          Type: Api
          Properties:
            Path: /company
            Method: post
            RestApiId: !Ref ApiGatewayApi
            RequestModel:
              Model: !Ref company
              Required: true
              ValidateBody: true
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        Sourcemap: true
        EntryPoints:
          - company.ts

This code works fine without the validation (in that it can successfully create a company entry), but when I try to reference the company model for validation I get the following error on sam deploy

Error: Failed to create changeset for the stack: companyAPI, ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Transform AWS::Serverless-2016-10-31 failed with: Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [PostCompanyFunction] is invalid. Event with id [Company] is invalid. Unable to set RequestModel [{u'Ref': u'company'}] on API method [post] for path [/company] because the related API does not contain valid Models.

I've tried removing !Ref from the model property of the request model, but that shows this error instead:

Error: Failed to create changeset for the stack: companyAPI, ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Unresolved resource dependencies [ServerlessRestApi] in the Outputs block of the template

I've read through just about every resource I can find on this and still haven't been able to make it work. Any help is greatly appreciated.

Resources used so far:

AWS SAM - Enforcing Request Validation in API Gateway Method by SAM Template

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-models

How to add a request validator in a AWS SAM template for AWS::Serverless::Api?

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-requestmodel.html

https://github.com/aws/aws-sam-cli/issues/364

I also found the request and PR that added that RequestModel property, but still no luck making it work.


Solution

  • Ok, I think I finally figured it out. ApiGatewayApi is a reserved word, so I had to rename that in my resources. You also have to use that resource name in your output templates. Here's the final template that does check the request body against a defined model. Also worth noting that this does not work when running with sam local start-api because the model resides in API Gateway, and that is not spun up in a docker container. This does, however, work when you run sam deploy.

    AWSTemplateFormatVersion: "2010-09-09"
    Transform: AWS::Serverless-2016-10-31
    Description: >
      company-service
    
    Resources:
      CompanyRestApi:
        Type: AWS::Serverless::Api
        Properties:
          StageName: Prod
          Models:
            companyModel:
              type: object
              required:
                - name
                - phone
              properties: 
                name:
                  type: string
                email:
                  type: string
                website:
                  type: string
                phone:
                  type: string
      # Company Endpoints
      PostCompanyFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: src/functions
          Handler: company.postCompany
          Runtime: nodejs14.x
          Architectures:
            - x86_64
          Events:
            Company:
              Type: Api
              Properties:
                Path: /company
                Method: post
                RestApiId: !Ref CompanyRestApi
                RequestModel:
                  Model: companyModel
                  Required: true
                  ValidateBody: true
        Metadata:
          BuildMethod: esbuild
          BuildProperties:
            Minify: true
            Target: "es2020"
            Sourcemap: true
            EntryPoints:
              - company.ts
    
    Outputs:
    # Company Outputs
      PostCompanyFunctionApi:
        Description: "API Gateway endpoint URL for Prod stage for PostCompaniesFunction"
        Value: !Sub "https://${CompanyRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/company"
      PutCompanyFunctionApi:
        Description: "API Gateway endpoint URL for Prod stage for