swift-server / swift-aws-lambda-runtime

Swift implementation of AWS Lambda Runtime
Apache License 2.0
1.12k stars 101 forks source link

Runtime not populating `APIGateway.V2.Request` properties #247

Closed biajoeknee closed 2 years ago

biajoeknee commented 2 years ago

Expected behavior

I expected that I would be able to make a POST request with the payload "{ "number": 3 }" (from tutorial), and AWS would create the APIGateway.V2.Request object for me, storing the payload in the APIGateway.V2.Request.body property.

Actual behavior

It's as if the Lambda is expecting the consumer of the API to provide all the APIGateway.V2.Request properties in the payload. The specific error I got when attempting to call the Lambda is:

{ "errorType": "FunctionError", "errorMessage": "requestDecoding(Swift.DecodingError.keyNotFound(CodingKeys(stringValue: \"version\", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: \"No value associated with key CodingKeys(stringValue: \\"version\\", intValue: nil) (\\"version\\").\", underlyingError: nil)))" }

If I provide values for all the APIGateway.V2.Request properties, then the Lambda works as expected: Successful payload:

{
        "routeKey":"POST /hello",
        "version":"2.0",
        "rawPath":"/hello",
        "body": "{\"number\": 3}",
        "stageVariables":{},
        "requestContext":{
            "timeEpoch":1587750461466,
            "domainPrefix":"hello",
            "accountId":"0123456789",
            "stage":"$default",
            "domainName":"hello.test.com",
            "apiId":"pb5dg6g3rg",
            "requestId":"LgLpnibOFiAEPCA=",
            "http":{
                "path":"/hello",
                "userAgent":"Paw/3.1.10 (Macintosh; OS X/10.15.4) GCDHTTPRequest",
                "method":"POST",
                "protocol":"HTTP/1.1",
                "sourceIp":"91.64.117.86"
            },
            "time":"24/Apr/2020:17:47:41 +0000"
        },
        "isBase64Encoded":false,
        "rawQueryString":"",
        "headers":{
            "host":"hello.test.com",
            "user-agent":"Paw/3.1.10 (Macintosh; OS X/10.15.4) GCDHTTPRequest",
            "content-length":"0"
        }
    }

Response:

{ "body": "{\"result\":9}", "statusCode": 200 }

Steps to reproduce

  1. Create Swift Lambda using code below.
  2. Build using Docker command in linked tutorial.
  3. Package using script in linked tutorial.
  4. Upload to AWS
  5. Create a new API Gateway resource.
  6. Add a POST method for it.
  7. Use the Lambda for the POST method.
  8. Click the new method in the API Gateway side pane.
  9. Click the "Test" button.
  10. Use the payload: { "number": 3 }
  11. Run the test.

If possible, minimal yet complete reproducer code (or URL to code)

Here is the code in my main file:

import Foundation
import AWSLambdaRuntime
import AWSLambdaEvents
import AsyncKit

Lambda.run(APILambda())

public final class APILambda: LambdaHandler {

    public func handle(context: Lambda.Context, event: APIGateway.V2.Request, callback: @escaping (Result<APIGateway.V2.Response, Error>) -> Void) {
        guard let body=event.body else { callback(.failure(GeneralError())); return }
        do {
            let input: Input=try JSONDecoder().decode(Input.self, from: body)
            let output=Output(result: input.number * input.number)
            let data=try JSONEncoder().encode(output)
            let string=String(data: data, encoding: .utf8)!
            let response: APIGateway.V2.Response = .init(statusCode: .ok, headers: .none, body: string, isBase64Encoded: .none, cookies: .none)
            callback(.success(response))
        } catch let error {
            callback(.failure(error))
        }
    }

    public struct Input: Codable {
      let number: Double
    }

    public struct Output: Codable {
      let result: Double
    }

    private struct GeneralError: Error {}

    public typealias In = APIGateway.V2.Request
    public typealias Out = APIGateway.V2.Response
}

Here is my package.swift:

import PackageDescription

let package = Package(
    name: "API",
    platforms: [
        .macOS(.v10_15)
    ],
    products: [
        .executable(
            name: "API",
            targets: ["API"]),
    ],
    dependencies: [
        .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", .upToNextMajor(from:"0.5.0")),
        .package(url: "https://github.com/vapor/postgres-kit.git", .upToNextMajor(from:"2.0.0"))
    ],
    targets: [
        .target(
            name: "API",
            dependencies: [
                .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
                .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-runtime"),
                .product(name: "PostgresKit", package: "postgres-kit")
            ])
    ]
)

SwiftAWSLambdaRuntime version/commit hash

0.5.2

Swift & OS version (output of swift --version && uname -a)

Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28) Target: x86_64-apple-darwin19.5.0 Darwin My-MBP-2.local 19.5.0 Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64 x86_64

biajoeknee commented 2 years ago

Resolved the issue. I assumed that the runtime would also work with a AWS REST API Gateway. I already had one up and running when I began the Swift Lambda Runtime tutorial, so I linked one of the endpoints to the Swift Lambda instead of creating a new HTTP API Gateway. The Lambda ran, but was not provided the APIGateway.V2.Request object; it has to be provided by the caller, apparently in its entirety.

I eventually ended up creating a HTTP API Gateway, and trying it that way; and just like in the tutorial, I didn't have to provide the entire APIGateway.V2.Request object in the request payload, just the body.