swift-aws-lambda-runtime icon indicating copy to clipboard operation
swift-aws-lambda-runtime copied to clipboard

Nullability of AWSGateway.Request properties

Open Andrea-Scuderi opened this issue 5 years ago • 14 comments

Hi, I made a small demo using Serverless framework with API Gateway, Lambda and DynamoDB.

Originally I tried to use APIGateway.Request as payload but I wasn't able to decode it as some of the properties are nil.

public let headers: HTTPHeaders
public let multiValueHeaders: HTTPMultiValueHeaders

https://github.com/swift-server/swift-aws-lambda-runtime/blob/master/Sources/AWSLambdaEvents/APIGateway.swift

I'll prepare a PR to fix it, if you agree to amend it.

My example: https://github.com/swift-sprinter/aws-serverless-swift-api-template

Andrea-Scuderi avatar May 31 '20 14:05 Andrea-Scuderi

Hi @Andrea-Scuderi, thanks for bringing this up. This is interesting. In my testing it has always been the case that both headers have been set and the documentation does not indicate otherwise...

https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html

Would you mind posting your example event here?

Just to be sure, did you use the RestAPI integration (v1) and not the recently released, cheaper HttpAPI integration (v2)? In case you are using the new HttpAPI integration you must use the events from (APIGateway.V2.Request and APIGateway.V2.Response) except otherwise enforced at the APIGateway level.

fabianfett avatar May 31 '20 20:05 fabianfett

Hi, I made a branch with the issue: https://github.com/swift-sprinter/aws-serverless-swift-api-template/tree/APIGateway.Response In this branch I made there is the issue with APIGateway.Request

I used at first APIGateway.V2.Request but I got another error as well.

Note that the integration configuration is made by the Serverless framework.

I'll check better, and send the full log.

Andrea-Scuderi avatar May 31 '20 20:05 Andrea-Scuderi

Here the logs:

Sun May 31 21:07:47 UTC 2020 : Endpoint response body before transformations: {"errorType":"FunctionError","errorMessage":"requestDecoding(Swift.DecodingError.valueNotFound(Swift.Dictionary<Swift.String, Swift.String>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: \"headers\", intValue: nil)], debugDescription: \"Expected Dictionary<String, String> value but found null instead.\", underlyingError: nil)))"}
Sun May 31 21:07:47 UTC 2020 : Lambda execution failed with status 200 due to customer function error: requestDecoding(Swift.DecodingError.valueNotFound(Swift.Dictionary<Swift.String, Swift.String>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "headers", intValue: nil)], debugDescription: "Expected Dictionary<String, String> value but found null instead.", underlyingError: nil))). Lambda request id: bf4a2226-87e4-4f0e-a53f-b641c885a0f2
Sun May 31 21:07:47 UTC 2020 : Method completed with status: 502

Andrea-Scuderi avatar May 31 '20 21:05 Andrea-Scuderi

Here the Cloud Formation created by the Serverless framework:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application",
  "Resources": {
    "ServerlessDeploymentBucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketEncryption": {
          "ServerSideEncryptionConfiguration": [
            {
              "ServerSideEncryptionByDefault": {
                "SSEAlgorithm": "AES256"
              }
            }
          ]
        }
      }
    },
    "ServerlessDeploymentBucketPolicy": {
      "Type": "AWS::S3::BucketPolicy",
      "Properties": {
        "Bucket": {
          "Ref": "ServerlessDeploymentBucket"
        },
        "PolicyDocument": {
          "Statement": [
            {
              "Action": "s3:*",
              "Effect": "Deny",
              "Principal": "*",
              "Resource": [
                {
                  "Fn::Join": [
                    "",
                    [
                      "arn:",
                      {
                        "Ref": "AWS::Partition"
                      },
                      ":s3:::",
                      {
                        "Ref": "ServerlessDeploymentBucket"
                      },
                      "/*"
                    ]
                  ]
                }
              ],
              "Condition": {
                "Bool": {
                  "aws:SecureTransport": false
                }
              }
            }
          ]
        }
      }
    },
    "CreateProductLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/swift-sprinter-rest-api-swift-dev-createProduct"
      }
    },
    "ReadProductLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/swift-sprinter-rest-api-swift-dev-readProduct"
      }
    },
    "UpdateProductLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/swift-sprinter-rest-api-swift-dev-updateProduct"
      }
    },
    "DeleteProductLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/swift-sprinter-rest-api-swift-dev-deleteProduct"
      }
    },
    "ListProductsLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/swift-sprinter-rest-api-swift-dev-listProducts"
      }
    },
    "IamRoleLambdaExecution": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": {
              "Fn::Join": [
                "-",
                [
                  "dev",
                  "swift-sprinter-rest-api-swift",
                  "lambda"
                ]
              ]
            },
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogStream",
                    "logs:CreateLogGroup"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/swift-sprinter-rest-api-swift-dev*:*"
                    }
                  ]
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:PutLogEvents"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/swift-sprinter-rest-api-swift-dev*:*:*"
                    }
                  ]
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                  ],
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "dynamodb:UpdateItem",
                    "dynamodb:PutItem",
                    "dynamodb:GetItem",
                    "dynamodb:DeleteItem",
                    "dynamodb:Query",
                    "dynamodb:Scan",
                    "dynamodb:DescribeTable"
                  ],
                  "Resource": [
                    {
                      "Fn::GetAtt": [
                        "ProductsTable",
                        "Arn"
                      ]
                    }
                  ]
                }
              ]
            }
          }
        ],
        "Path": "/",
        "RoleName": {
          "Fn::Join": [
            "-",
            [
              "swift-sprinter-rest-api-swift",
              "dev",
              {
                "Ref": "AWS::Region"
              },
              "lambdaRole"
            ]
          ]
        }
      }
    },
    "SwiftDashlambdaDashruntimeLambdaLayer": {
      "Type": "AWS::Lambda::LayerVersion",
      "Properties": {
        "Content": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/swift-sprinter-rest-api-swift/dev/1590958619571-2020-05-31T20:56:59.571Z/swift-lambda-runtime.zip"
        },
        "LayerName": "aws-swift-sprinter-lambda-runtime",
        "Description": "AWS Lambda Custom Runtime for Swift-Sprinter"
      }
    },
    "CreateProductLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/swift-sprinter-rest-api-swift/dev/1590958619571-2020-05-31T20:56:59.571Z/createProduct.zip"
        },
        "FunctionName": "swift-sprinter-rest-api-swift-dev-createProduct",
        "Handler": "build/Products.create",
        "MemorySize": 256,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        },
        "Runtime": "provided",
        "Timeout": 6,
        "Description": "[dev] Create Product",
        "Environment": {
          "Variables": {
            "PRODUCTS_TABLE_NAME": "swift-sprinter-products-table-dev"
          }
        },
        "Layers": [
          {
            "Ref": "SwiftDashlambdaDashruntimeLambdaLayer"
          }
        ]
      },
      "DependsOn": [
        "CreateProductLogGroup",
        "IamRoleLambdaExecution"
      ]
    },
    "CreateProductLambdaVersionFcAKu9BmWmV6XcT6zYMXiTOTzaPApEwvNt7kOFw25Q": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "CreateProductLambdaFunction"
        },
        "CodeSha256": "gefvnn7eGPkN6JLAHNRTxy6cB8BmmvpFJZ/W7ilyluM=",
        "Description": "[dev] Create Product"
      }
    },
    "ReadProductLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/swift-sprinter-rest-api-swift/dev/1590958619571-2020-05-31T20:56:59.571Z/readProduct.zip"
        },
        "FunctionName": "swift-sprinter-rest-api-swift-dev-readProduct",
        "Handler": "build/Products.read",
        "MemorySize": 256,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        },
        "Runtime": "provided",
        "Timeout": 6,
        "Description": "[dev] Get Product",
        "Environment": {
          "Variables": {
            "PRODUCTS_TABLE_NAME": "swift-sprinter-products-table-dev"
          }
        },
        "Layers": [
          {
            "Ref": "SwiftDashlambdaDashruntimeLambdaLayer"
          }
        ]
      },
      "DependsOn": [
        "ReadProductLogGroup",
        "IamRoleLambdaExecution"
      ]
    },
    "ReadProductLambdaVersionClu1XVGJNfVHTNn9CHbI4lADKenPdpXIa8xYenH5Y": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "ReadProductLambdaFunction"
        },
        "CodeSha256": "gefvnn7eGPkN6JLAHNRTxy6cB8BmmvpFJZ/W7ilyluM=",
        "Description": "[dev] Get Product"
      }
    },
    "UpdateProductLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/swift-sprinter-rest-api-swift/dev/1590958619571-2020-05-31T20:56:59.571Z/updateProduct.zip"
        },
        "FunctionName": "swift-sprinter-rest-api-swift-dev-updateProduct",
        "Handler": "build/Products.update",
        "MemorySize": 256,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        },
        "Runtime": "provided",
        "Timeout": 6,
        "Description": "[dev] Update Product",
        "Environment": {
          "Variables": {
            "PRODUCTS_TABLE_NAME": "swift-sprinter-products-table-dev"
          }
        },
        "Layers": [
          {
            "Ref": "SwiftDashlambdaDashruntimeLambdaLayer"
          }
        ]
      },
      "DependsOn": [
        "UpdateProductLogGroup",
        "IamRoleLambdaExecution"
      ]
    },
    "UpdateProductLambdaVersionVEf8POcBJrzwD3lJNxbquVWI9W7Bw5hH3EupbComduI": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "UpdateProductLambdaFunction"
        },
        "CodeSha256": "gefvnn7eGPkN6JLAHNRTxy6cB8BmmvpFJZ/W7ilyluM=",
        "Description": "[dev] Update Product"
      }
    },
    "DeleteProductLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/swift-sprinter-rest-api-swift/dev/1590958619571-2020-05-31T20:56:59.571Z/deleteProduct.zip"
        },
        "FunctionName": "swift-sprinter-rest-api-swift-dev-deleteProduct",
        "Handler": "build/Products.delete",
        "MemorySize": 256,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        },
        "Runtime": "provided",
        "Timeout": 6,
        "Description": "[dev] Delete Product",
        "Environment": {
          "Variables": {
            "PRODUCTS_TABLE_NAME": "swift-sprinter-products-table-dev"
          }
        },
        "Layers": [
          {
            "Ref": "SwiftDashlambdaDashruntimeLambdaLayer"
          }
        ]
      },
      "DependsOn": [
        "DeleteProductLogGroup",
        "IamRoleLambdaExecution"
      ]
    },
    "DeleteProductLambdaVersionYhN6H7XkSm7QdCjQiJAw7MV2vQOLS2FEuYh0nwwmus": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "DeleteProductLambdaFunction"
        },
        "CodeSha256": "gefvnn7eGPkN6JLAHNRTxy6cB8BmmvpFJZ/W7ilyluM=",
        "Description": "[dev] Delete Product"
      }
    },
    "ListProductsLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/swift-sprinter-rest-api-swift/dev/1590958619571-2020-05-31T20:56:59.571Z/listProducts.zip"
        },
        "FunctionName": "swift-sprinter-rest-api-swift-dev-listProducts",
        "Handler": "build/Products.list",
        "MemorySize": 256,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        },
        "Runtime": "provided",
        "Timeout": 6,
        "Description": "[dev] List Products",
        "Environment": {
          "Variables": {
            "PRODUCTS_TABLE_NAME": "swift-sprinter-products-table-dev"
          }
        },
        "Layers": [
          {
            "Ref": "SwiftDashlambdaDashruntimeLambdaLayer"
          }
        ]
      },
      "DependsOn": [
        "ListProductsLogGroup",
        "IamRoleLambdaExecution"
      ]
    },
    "ListProductsLambdaVersionDmh2m13atNPTocuEI0Vz9o1jkL12SByEJd3ocgWn48": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "ListProductsLambdaFunction"
        },
        "CodeSha256": "gefvnn7eGPkN6JLAHNRTxy6cB8BmmvpFJZ/W7ilyluM=",
        "Description": "[dev] List Products"
      }
    },
    "ApiGatewayRestApi": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Name": "dev-swift-sprinter-rest-api-swift",
        "EndpointConfiguration": {
          "Types": [
            "EDGE"
          ]
        },
        "Policy": ""
      }
    },
    "ApiGatewayResourceProducts": {
      "Type": "AWS::ApiGateway::Resource",
      "Properties": {
        "ParentId": {
          "Fn::GetAtt": [
            "ApiGatewayRestApi",
            "RootResourceId"
          ]
        },
        "PathPart": "products",
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        }
      }
    },
    "ApiGatewayResourceProductsSkuVar": {
      "Type": "AWS::ApiGateway::Resource",
      "Properties": {
        "ParentId": {
          "Ref": "ApiGatewayResourceProducts"
        },
        "PathPart": "{sku}",
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        }
      }
    },
    "ApiGatewayMethodProductsOptions": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "AuthorizationType": "NONE",
        "HttpMethod": "OPTIONS",
        "MethodResponses": [
          {
            "StatusCode": "200",
            "ResponseParameters": {
              "method.response.header.Access-Control-Allow-Origin": true,
              "method.response.header.Access-Control-Allow-Headers": true,
              "method.response.header.Access-Control-Allow-Methods": true
            },
            "ResponseModels": {}
          }
        ],
        "RequestParameters": {},
        "Integration": {
          "Type": "MOCK",
          "RequestTemplates": {
            "application/json": "{statusCode:200}"
          },
          "ContentHandling": "CONVERT_TO_TEXT",
          "IntegrationResponses": [
            {
              "StatusCode": "200",
              "ResponseParameters": {
                "method.response.header.Access-Control-Allow-Origin": "'*'",
                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
                "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST'"
              },
              "ResponseTemplates": {
                "application/json": "#set($origin = $input.params(\"Origin\"))\n#if($origin == \"\") #set($origin = $input.params(\"origin\")) #end\n#if($origin.matches(\".*\")) #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end"
              }
            }
          ]
        },
        "ResourceId": {
          "Ref": "ApiGatewayResourceProducts"
        },
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        }
      }
    },
    "ApiGatewayMethodProductsSkuVarOptions": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "AuthorizationType": "NONE",
        "HttpMethod": "OPTIONS",
        "MethodResponses": [
          {
            "StatusCode": "200",
            "ResponseParameters": {
              "method.response.header.Access-Control-Allow-Origin": true,
              "method.response.header.Access-Control-Allow-Headers": true,
              "method.response.header.Access-Control-Allow-Methods": true
            },
            "ResponseModels": {}
          }
        ],
        "RequestParameters": {},
        "Integration": {
          "Type": "MOCK",
          "RequestTemplates": {
            "application/json": "{statusCode:200}"
          },
          "ContentHandling": "CONVERT_TO_TEXT",
          "IntegrationResponses": [
            {
              "StatusCode": "200",
              "ResponseParameters": {
                "method.response.header.Access-Control-Allow-Origin": "'*'",
                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
                "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,DELETE,GET'"
              },
              "ResponseTemplates": {
                "application/json": "#set($origin = $input.params(\"Origin\"))\n#if($origin == \"\") #set($origin = $input.params(\"origin\")) #end\n#if($origin.matches(\".*\")) #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end"
              }
            }
          ]
        },
        "ResourceId": {
          "Ref": "ApiGatewayResourceProductsSkuVar"
        },
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        }
      }
    },
    "ApiGatewayMethodProductsPost": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "HttpMethod": "POST",
        "RequestParameters": {},
        "ResourceId": {
          "Ref": "ApiGatewayResourceProducts"
        },
        "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": [
                    "CreateProductLambdaFunction",
                    "Arn"
                  ]
                },
                "/invocations"
              ]
            ]
          }
        },
        "MethodResponses": []
      }
    },
    "ApiGatewayMethodProductsSkuVarGet": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "HttpMethod": "GET",
        "RequestParameters": {},
        "ResourceId": {
          "Ref": "ApiGatewayResourceProductsSkuVar"
        },
        "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": [
                    "ReadProductLambdaFunction",
                    "Arn"
                  ]
                },
                "/invocations"
              ]
            ]
          }
        },
        "MethodResponses": []
      }
    },
    "ApiGatewayMethodProductsPut": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "HttpMethod": "PUT",
        "RequestParameters": {},
        "ResourceId": {
          "Ref": "ApiGatewayResourceProducts"
        },
        "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": [
                    "UpdateProductLambdaFunction",
                    "Arn"
                  ]
                },
                "/invocations"
              ]
            ]
          }
        },
        "MethodResponses": []
      }
    },
    "ApiGatewayMethodProductsSkuVarDelete": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "HttpMethod": "DELETE",
        "RequestParameters": {},
        "ResourceId": {
          "Ref": "ApiGatewayResourceProductsSkuVar"
        },
        "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": [
                    "DeleteProductLambdaFunction",
                    "Arn"
                  ]
                },
                "/invocations"
              ]
            ]
          }
        },
        "MethodResponses": []
      }
    },
    "ApiGatewayMethodProductsGet": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "HttpMethod": "GET",
        "RequestParameters": {},
        "ResourceId": {
          "Ref": "ApiGatewayResourceProducts"
        },
        "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": [
                    "ListProductsLambdaFunction",
                    "Arn"
                  ]
                },
                "/invocations"
              ]
            ]
          }
        },
        "MethodResponses": []
      }
    },
    "ApiGatewayDeployment1590958611566": {
      "Type": "AWS::ApiGateway::Deployment",
      "Properties": {
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "StageName": "dev"
      },
      "DependsOn": [
        "ApiGatewayMethodProductsOptions",
        "ApiGatewayMethodProductsSkuVarOptions",
        "ApiGatewayMethodProductsPost",
        "ApiGatewayMethodProductsSkuVarGet",
        "ApiGatewayMethodProductsPut",
        "ApiGatewayMethodProductsSkuVarDelete",
        "ApiGatewayMethodProductsGet"
      ]
    },
    "CreateProductLambdaPermissionApiGateway": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "CreateProductLambdaFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:",
              {
                "Ref": "AWS::Partition"
              },
              ":execute-api:",
              {
                "Ref": "AWS::Region"
              },
              ":",
              {
                "Ref": "AWS::AccountId"
              },
              ":",
              {
                "Ref": "ApiGatewayRestApi"
              },
              "/*/*"
            ]
          ]
        }
      }
    },
    "ReadProductLambdaPermissionApiGateway": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "ReadProductLambdaFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:",
              {
                "Ref": "AWS::Partition"
              },
              ":execute-api:",
              {
                "Ref": "AWS::Region"
              },
              ":",
              {
                "Ref": "AWS::AccountId"
              },
              ":",
              {
                "Ref": "ApiGatewayRestApi"
              },
              "/*/*"
            ]
          ]
        }
      }
    },
    "UpdateProductLambdaPermissionApiGateway": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "UpdateProductLambdaFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:",
              {
                "Ref": "AWS::Partition"
              },
              ":execute-api:",
              {
                "Ref": "AWS::Region"
              },
              ":",
              {
                "Ref": "AWS::AccountId"
              },
              ":",
              {
                "Ref": "ApiGatewayRestApi"
              },
              "/*/*"
            ]
          ]
        }
      }
    },
    "DeleteProductLambdaPermissionApiGateway": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "DeleteProductLambdaFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:",
              {
                "Ref": "AWS::Partition"
              },
              ":execute-api:",
              {
                "Ref": "AWS::Region"
              },
              ":",
              {
                "Ref": "AWS::AccountId"
              },
              ":",
              {
                "Ref": "ApiGatewayRestApi"
              },
              "/*/*"
            ]
          ]
        }
      }
    },
    "ListProductsLambdaPermissionApiGateway": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "ListProductsLambdaFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:",
              {
                "Ref": "AWS::Partition"
              },
              ":execute-api:",
              {
                "Ref": "AWS::Region"
              },
              ":",
              {
                "Ref": "AWS::AccountId"
              },
              ":",
              {
                "Ref": "ApiGatewayRestApi"
              },
              "/*/*"
            ]
          ]
        }
      }
    },
    "ProductsTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "TableName": "swift-sprinter-products-table-dev",
        "AttributeDefinitions": [
          {
            "AttributeName": "sku",
            "AttributeType": "S"
          }
        ],
        "KeySchema": [
          {
            "AttributeName": "sku",
            "KeyType": "HASH"
          }
        ],
        "BillingMode": "PAY_PER_REQUEST"
      }
    }
  },
  "Outputs": {
    "ServerlessDeploymentBucketName": {
      "Value": {
        "Ref": "ServerlessDeploymentBucket"
      }
    },
    "SwiftDashlambdaDashruntimeLambdaLayerQualifiedArn": {
      "Description": "Current Lambda layer version",
      "Value": {
        "Ref": "SwiftDashlambdaDashruntimeLambdaLayer"
      }
    },
    "CreateProductLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "CreateProductLambdaVersionFcAKu9BmWmV6XcT6zYMXiTOTzaPApEwvNt7kOFw25Q"
      }
    },
    "ReadProductLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "ReadProductLambdaVersionClu1XVGJNfVHTNn9CHbI4lADKenPdpXIa8xYenH5Y"
      }
    },
    "UpdateProductLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "UpdateProductLambdaVersionVEf8POcBJrzwD3lJNxbquVWI9W7Bw5hH3EupbComduI"
      }
    },
    "DeleteProductLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "DeleteProductLambdaVersionYhN6H7XkSm7QdCjQiJAw7MV2vQOLS2FEuYh0nwwmus"
      }
    },
    "ListProductsLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "ListProductsLambdaVersionDmh2m13atNPTocuEI0Vz9o1jkL12SByEJd3ocgWn48"
      }
    },
    "ServiceEndpoint": {
      "Description": "URL of the service endpoint",
      "Value": {
        "Fn::Join": [
          "",
          [
            "https://",
            {
              "Ref": "ApiGatewayRestApi"
            },
            ".execute-api.",
            {
              "Ref": "AWS::Region"
            },
            ".",
            {
              "Ref": "AWS::URLSuffix"
            },
            "/dev"
          ]
        ]
      }
    }
  }
}

Andrea-Scuderi avatar May 31 '20 21:05 Andrea-Scuderi

Okay, would you mind to deploy a simple String Lambda to your endpoint so that we can print the original event? This way we can have a look at what matched and what did not match.

import AWSLambdaRuntime

Lambda.run { (context, event: String, callback) in
  context.logger.warning("event: \(event)")
  callback(.success(#"{"statusCode": 200, "body": "event logged"}"#))
}

fabianfett avatar May 31 '20 21:05 fabianfett

Sure, note that the error is clear. The 'header' dictionary is nil In fact in latest code I solved by just creating the event I needed:

public extension APIGateway {
    struct SimpleRequest: Codable {
        public let body: String?
        public let pathParameters: [String: String]?
    }
}

Andrea-Scuderi avatar May 31 '20 21:05 Andrea-Scuderi

@Andrea-Scuderi I've seen the error. Yes, there is a problem... But if we just remove properties if there is a problem with one, we might end up with no properties overall...

fabianfett avatar May 31 '20 21:05 fabianfett

I suggest to check with the AWS guys for the right specification of those events. The official documentation is quite unclear.

Andrea-Scuderi avatar May 31 '20 21:05 Andrea-Scuderi

cc @bmoffatt

tomerd avatar May 31 '20 21:05 tomerd

@Andrea-Scuderi Is this still an issue? If so do you have the incoming request payload handy?

fabianfett avatar Aug 03 '20 18:08 fabianfett

@fabianfett Yes, it is still an issue.

Here the payload:

2020-08-08T12:31:34+0000 warning: lifecycleIteration=1 awsRequestID=acd877c6-e54f-47aa-bdc0-467bfa537b04 awsTraceID=Root=1-5f2e9b26-3d35d2de2ebafebee4ce1390;Parent=1dcca0a36cbb8e11;Sampled=0 event: {"resource":"/products","path":"/products","httpMethod":"GET","headers":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-gb","CloudFront-Forwarded-Proto":"https","CloudFront-Is-Desktop-Viewer":"true","CloudFront-Is-Mobile-Viewer":"false","CloudFront-Is-SmartTV-Viewer":"false","CloudFront-Is-Tablet-Viewer":"false","CloudFront-Viewer-Country":"IT","Host":"tth4frgaw0.execute-api.us-east-1.amazonaws.com","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15","Via":"2.0 3a2b7bab76093d39e8da0874d82ee34d.cloudfront.net (CloudFront)","X-Amz-Cf-Id":"jYCwSHUHZ4FzIhvuv6ZiNmhAoROo607LWSFL7xJIZnGTvhZtlRMwjQ==","X-Amzn-Trace-Id":"Root=1-5f2e9b26-3d35d2de2ebafebee4ce1390","X-Forwarded-For":"37.179.128.97, 130.176.90.132","X-Forwarded-Port":"443","X-Forwarded-Proto":"https"},"multiValueHeaders":{"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-gb"],"CloudFront-Forwarded-Proto":["https"],"CloudFront-Is-Desktop-Viewer":["true"],"CloudFront-Is-Mobile-Viewer":["false"],"CloudFront-Is-SmartTV-Viewer":["false"],"CloudFront-Is-Tablet-Viewer":["false"],"CloudFront-Viewer-Country":["IT"],"Host":["tth4frgaw0.execute-api.us-east-1.amazonaws.com"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15"],"Via":["2.0 3a2b7bab76093d39e8da0874d82ee34d.cloudfront.net (CloudFront)"],"X-Amz-Cf-Id":["jYCwSHUHZ4FzIhvuv6ZiNmhAoROo607LWSFL7xJIZnGTvhZtlRMwjQ=="],"X-Amzn-Trace-Id":["Root=1-5f2e9b26-3d35d2de2ebafebee4ce1390"],"X-Forwarded-For":["37.179.128.97, 130.176.90.132"],"X-Forwarded-Port":["443"],"X-Forwarded-Proto":["https"]},"queryStringParameters":null,"multiValueQueryStringParameters":null,"pathParameters":null,"stageVariables":null,"requestContext":{"resourceId":"qimgur","resourcePath":"/products","httpMethod":"GET","extendedRequestId":"Q80t-FPLoAMF7OQ=","requestTime":"08/Aug/2020:12:31:34 +0000","path":"/dev/products","accountId":"339065908707","protocol":"HTTP/1.1","stage":"dev","domainPrefix":"tth4frgaw0","requestTimeEpoch":1596889894141,"requestId":"6217a5c2-ff35-42bd-866c-f2b1ceefe6a6","identity":{"cognitoIdentityPoolId":null,"accountId":null,"cognitoIdentityId":null,"caller":null,"sourceIp":"37.179.128.97","principalOrgId":null,"accessKey":null,"cognitoAuthenticationType":null,"cognitoAuthenticationProvider":null,"userArn":null,"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15","user":null},"domainName":"tth4frgaw0.execute-api.us-east-1.amazonaws.com","apiId":"tth4frgaw0"},"body":null,"isBase64Encoded":false}

printed by:

import AWSLambdaRuntime

let lambda: Lambda.StringClosure = { (context, event: String, callback) in
    context.logger.warning("event: \(event)")
    let result: Result<String, Error> = .success(#"{"statusCode": 200, "body": "event logged"}"#)
    callback(result)
}
Lambda.run(lambda)

Andrea-Scuderi avatar Aug 08 '20 12:08 Andrea-Scuderi

Hi @Andrea-Scuderi,

the payload, you uploaded is an APIGateway v1 payload. I've just created a test case for it, to verify that it is working... See attached code.

Does this solve your problem?

    static let otherEventBody = """
    {
      "resource":"/products",
      "path":"/products",
      "httpMethod":"GET",
      "headers":{
        "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Encoding":"gzip, deflate, br",
        "Accept-Language":"en-gb",
        "CloudFront-Forwarded-Proto":"https",
        "CloudFront-Is-Desktop-Viewer":"true",
        "CloudFront-Is-Mobile-Viewer":"false",
        "CloudFront-Is-SmartTV-Viewer":"false",
        "CloudFront-Is-Tablet-Viewer":"false",
        "CloudFront-Viewer-Country":"IT",
        "Host":"tth4frgaw0.execute-api.us-east-1.amazonaws.com",
        "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15",
        "Via":"2.0 3a2b7bab76093d39e8da0874d82ee34d.cloudfront.net (CloudFront)",
        "X-Amz-Cf-Id":"jYCwSHUHZ4FzIhvuv6ZiNmhAoROo607LWSFL7xJIZnGTvhZtlRMwjQ==",
        "X-Amzn-Trace-Id":"Root=1-5f2e9b26-3d35d2de2ebafebee4ce1390",
        "X-Forwarded-For":"37.179.128.97, 130.176.90.132",
        "X-Forwarded-Port":"443",
        "X-Forwarded-Proto":"https"
      },
      "multiValueHeaders":{
        "Accept":[
          "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
        ],
        "Accept-Encoding":[
          "gzip, deflate, br"
        ],
        "Accept-Language":[
          "en-gb"
        ],
        "CloudFront-Forwarded-Proto":[
          "https"
        ],
        "CloudFront-Is-Desktop-Viewer":[
          "true"
        ],
        "CloudFront-Is-Mobile-Viewer":[
          "false"
        ],
        "CloudFront-Is-SmartTV-Viewer":[
          "false"
        ],
        "CloudFront-Is-Tablet-Viewer":[
          "false"
        ],
        "CloudFront-Viewer-Country":[
          "IT"
        ],
        "Host":[
          "tth4frgaw0.execute-api.us-east-1.amazonaws.com"
        ],
        "User-Agent":[
          "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15"
        ],
        "Via":[
          "2.0 3a2b7bab76093d39e8da0874d82ee34d.cloudfront.net (CloudFront)"
        ],
        "X-Amz-Cf-Id":[
          "jYCwSHUHZ4FzIhvuv6ZiNmhAoROo607LWSFL7xJIZnGTvhZtlRMwjQ=="
        ],
        "X-Amzn-Trace-Id":[
          "Root=1-5f2e9b26-3d35d2de2ebafebee4ce1390"
        ],
        "X-Forwarded-For":[
          "37.179.128.97, 130.176.90.132"
        ],
        "X-Forwarded-Port":[
          "443"
        ],
        "X-Forwarded-Proto":[
          "https"
        ]
      },
      "queryStringParameters":null,
      "multiValueQueryStringParameters":null,
      "pathParameters":null,
      "stageVariables":null,
      "requestContext":{
        "resourceId":"qimgur",
        "resourcePath":"/products",
        "httpMethod":"GET",
        "extendedRequestId":"Q80t-FPLoAMF7OQ=",
        "requestTime":"08/Aug/2020:12:31:34 +0000",
        "path":"/dev/products",
        "accountId":"339065908707",
        "protocol":"HTTP/1.1",
        "stage":"dev",
        "domainPrefix":"tth4frgaw0",
        "requestTimeEpoch":1596889894141,
        "requestId":"6217a5c2-ff35-42bd-866c-f2b1ceefe6a6",
        "identity":{
          "cognitoIdentityPoolId":null,
          "accountId":null,
          "cognitoIdentityId":null,
          "caller":null,
          "sourceIp":"37.179.128.97",
          "principalOrgId":null,
          "accessKey":null,
          "cognitoAuthenticationType":null,
          "cognitoAuthenticationProvider":null,
          "userArn":null,
          "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15",
          "user":null
        },
        "domainName":"tth4frgaw0.execute-api.us-east-1.amazonaws.com",
        "apiId":"tth4frgaw0"
      },
      "body":null,
      "isBase64Encoded":false
    }
    """

    func testRequestDecodingOtherRequest() {
        let data = APIGatewayTests.otherEventBody.data(using: .utf8)!
        var req: APIGateway.Request?
        XCTAssertNoThrow(req = try JSONDecoder().decode(APIGateway.Request.self, from: data))
    }

fabianfett avatar Aug 10 '20 07:08 fabianfett

@fabianfett Hi, I am working on API Services with API Gateway(Http Api), Lambda and DynamoDB. I have created a Custom Authoriser in swift using swift lambda runtime I am using APIGateway.V2.Request as payload but I wasn't able to decode it as isBase64Encoded property is nil from Api Gateway payload but In APIGateway.V2.Request object it is must so I am getting error Swift.DecodingError.keyNotFound :

2021-10-15T11:29:30+0000 warning Lambda : lifecycleIteration=1 lambda handler returned an error: requestDecoding(Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "isBase64Encoded", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"isBase64Encoded\", intValue: nil) (\"isBase64Encoded\").", underlyingError: nil)))

for example my authoriser code is:

import AWSLambdaRuntime
import AWSLambdaEvents

Lambda.run { (context, request: APIGateway.V2.Request, callback: @escaping (Result<APIGateway.V2.Response, Error>) -> Void) in
    let responseBody = """
    {
      "isAuthorized": true,
      "context": {
        "exampleKey": "exampleValue"
      }
    }
   """
    callback(.success(APIGateway.V2.Response(statusCode: .ok, body:responseBody)))
}

Currently I am using my own copy of ApiRequest with optional isBase64Encoded. Is there a better approach to fix it?

I am using Http api authorise with simple response, I have looked into aws docs here https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html and found that in payload version 2.0 they are not sending isBase64Encoded as well Authorizer-Payload-2.0.txt

I'll prepare a PR to fix it , if you agree to amend it.

vikas4goyal avatar Oct 15 '21 12:10 vikas4goyal

@vikas4goyal I think we should have special request and response types for the Lambda authorizers. I'd be happy to review such a pr. However please raise this pr against this repo, since all new event development is done there:

https://github.com/swift-server/swift-aws-lambda-events

If you have any questions please reach out!

fabianfett avatar Oct 15 '21 13:10 fabianfett