swift-server / swift-aws-lambda-runtime

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

Event JSON #324

Closed RyPoints closed 1 month ago

RyPoints commented 2 months ago

Expected behavior

Received from a Lambda Event JSON test. The question is about how to get the Event JSON.

[SwiftCode] Received client context: nil
2024-05-05T22:50:14+0000 debug SwiftCode : [SwiftCode] Received client context: nil
2024-05-05T22:50:14+0000 error SwiftCode : [SwiftCode] Unknown event: 
{} Request(body: nil, rawQueryString: nil, httpMethod: nil, headers: nil, queryStringParameters: nil, 
pathParameters: nil, stageVariables: nil, isBase64Encoded: nil, eventSource: nil)
@main
struct SwiftCode: LambdaHandler {

    typealias Event = Request
    typealias Output = Response

    func handle(_ event: Request, context: LambdaContext) async throws -> Response {
    //Logging code
    }
}

It works fine from the API Gateway, but how does one get the Lambda Event JSON?

Perhaps changing the Request type will achieve this somehow, but often interfaces in other languages than Swift provide a method to both access the API Gateway Body and the Lambda Event JSON from the same Lambda handler structure.

Actual behavior

Actual behavior is Lambda events seem to have no method to access Lambda Console Event JSON.

Steps to reproduce

Log all variables when in the AWS Lambda Event JSON console and passing an event JSON. All variables are nil.

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

See above.

What version of this project (swift-aws-lambda-runtime) are you using?

.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime", branch: "main"),

Swift version

5.10

Amazon Linux 2 docker image version

Amazon Linux 2 (Karoo) and now Amazon Linux 2023.4.20240401 where it's also running fine.

sebsto commented 1 month ago

Hello,

By default, the Lambda runtime serialize the JSON and pass it to your handler as a Request type. The Swift AWS Lambda Event library has many Swift implementation for standard event types.

The runtime hides the raw JSON from your code, you can only access the data by using the correct Swift struct that matches the event JSON.

If you want to use different types, here are a couple of samples

HTTPAPI :

import AWSLambdaEvents
import AWSLambdaRuntime
import Foundation

@main
struct HttpApiLambda: LambdaHandler {
    init() {}
    init(context: LambdaInitializationContext) async throws {
        context.logger.info(
            "Log Level env var : \(ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "info" )")
    }

    // the return value must be either APIGatewayV2Response or any Encodable struct
    func handle(_ event: APIGatewayV2Request, context: AWSLambdaRuntimeCore.LambdaContext) async throws -> APIGatewayV2Response {

        var header = HTTPHeaders()
        do {
            context.logger.debug("HTTP API Message received")

            header["content-type"] = "application/json"

            // echo the request in the response
            let data = try JSONEncoder().encode(event)
            let response = String(data: data, encoding: .utf8)

            // if you want control on the status code and headers, return an APIGatewayV2Response
            // otherwise, just return any Encodable struct, the runtime will wrap it for you
            return APIGatewayV2Response(statusCode: .ok, headers: header, body: response)

        } catch {
            // should never happen as the decoding was made by the runtime
            // when the input event is malformed, this function is not even called
            header["content-type"] = "text/plain"
            return APIGatewayV2Response(statusCode: .badRequest, headers: header, body: "\(error.localizedDescription)")

        }
    }
}

SQS

import AWSLambdaEvents
import AWSLambdaRuntime
import Foundation

@main
struct SQSLambda: LambdaHandler {
    typealias Event = SQSEvent
    typealias Output = Void

    init() {}
    init(context: LambdaInitializationContext) async throws {
        context.logger.info(
            "Log Level env var : \(ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "info" )")
    }

    func handle(_ event: Event, context: AWSLambdaRuntimeCore.LambdaContext) async throws -> Output {

        context.logger.info("Log Level env var : \(ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "not defined" )" )
        context.logger.debug("SQS Message received, with \(event.records.count) record")

        for msg in event.records {
            context.logger.debug("Message ID   : \(msg.messageId)")
            context.logger.debug("Message body : \(msg.body)")
        }
    }
}

Lambda URL

import AWSLambdaEvents
import AWSLambdaRuntime
import Foundation

@main
struct UrlLambda: LambdaHandler {
  init() {}
  init(context: LambdaInitializationContext) async throws {
    context.logger.info(
      "Log Level env var : \(ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "info" )")
  }

  // the return value must be either APIGatewayV2Response or any Encodable struct
  func handle(_ event: FunctionURLRequest, context: AWSLambdaRuntimeCore.LambdaContext) async throws
    -> FunctionURLResponse
  {

    var header = HTTPHeaders()
    do {
      context.logger.debug("HTTP API Message received")

      header["content-type"] = "application/json"

      // echo the request in the response
      let data = try JSONEncoder().encode(event)
      let response = String(data: data, encoding: .utf8)

      // if you want control on the status code and headers, return an APIGatewayV2Response
      // otherwise, just return any Encodable struct, the runtime will wrap it for you
      return FunctionURLResponse(statusCode: .ok, headers: header, body: response)

    } catch {
      // should never happen as the decoding was made by the runtime
      // when the input event is malformed, this function is not even called
      header["content-type"] = "text/plain"
      return FunctionURLResponse(
        statusCode: .badRequest, headers: header, body: "\(error.localizedDescription)")

    }
  }
}
RyPoints commented 1 month ago

Will take a look at your samples when I have a moment. I'm looking at a V1 REST API and the Lambda Event JSON, so slightly different than the samples, but the answer might be in the samples if I review more. Was attempting to construct some very general function that worked for all scenarios at once for verification.

sebsto commented 1 month ago

Hello @RyPoints You're correct, the example I gave above is for API Gateway v2. There is a struct definition for API Gateway v1 also. Check this file in the Lambda events project.

If you want to access the raw byte stream and be in charge of the decoding yourself, you can do that too.

Either you use the SimpleLambdaHandler with a String parameter, just like in this example https://github.com/swift-server/swift-aws-lambda-runtime/blob/main/Examples/Echo/Lambda.swift Or you can implement a ByteBufferLambdaHandler that gives you access to the raw byte buffer from SwiftNIO

https://github.com/swift-server/swift-aws-lambda-runtime/blob/221978224cf82027f46c0aa54def83476fb74412/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift#L401

RyPoints commented 1 month ago

Thanks for the additional info. I will definitely take a look at that as well. Like the dev over here. Catching up.