swift-server / swift-aws-lambda-runtime

Swift implementation of AWS Lambda Runtime
https://swiftpackageindex.com/swift-server/swift-aws-lambda-runtime
Apache License 2.0
1.15k stars 105 forks source link

Nullability of AWSGateway.Request properties #102

Closed Andrea-Scuderi closed 5 months ago

Andrea-Scuderi commented 4 years ago

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

fabianfett commented 4 years ago

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.

Andrea-Scuderi commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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"
          ]
        ]
      }
    }
  }
}
fabianfett commented 4 years ago

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"}"#))
}
Andrea-Scuderi commented 4 years ago

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]?
    }
}
fabianfett commented 4 years ago

@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...

Andrea-Scuderi commented 4 years ago

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

tomerd commented 4 years ago

cc @bmoffatt

fabianfett commented 4 years ago

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

Andrea-Scuderi commented 4 years ago

@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)
fabianfett commented 4 years ago

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))
    }
vikas4goyal commented 3 years ago

@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.

fabianfett commented 3 years ago

@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!

sebsto commented 5 months ago

Closing this.

@Andrea-Scuderi please reopen an issue on the Lambda event project if still an issue for you (or even better, send a PR :-) ) There is no harm to set headers and multiValueHeaders as optional in the Swift struct to accommodate for different payload. There is no AWS doc that mention if the fields are mandatory or not in the request (it does for the response only)

@vikas4goyal There is now a type for Lambda authorizers in the Lambda event project

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