Skip to content
This repository was archived by the owner on Jul 14, 2020. It is now read-only.

Added S3Event type. #18

Merged
merged 1 commit into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions Examples/EventSources/Sources/EventSources/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,23 @@ let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { try! group.syncShutdownGracefully() }
let logger = Logger(label: "AWSLambda.EventSources")

struct SNSBody: Codable {
let name: String
let whatevar: String
}

func handleSNS(event: SNS.Event, ctx: Context) -> EventLoopFuture<Void> {
ctx.logger.info("Payload: \(String(describing: event))")

return ctx.eventLoop.makeSucceededFuture(Void())
do {
let message = event.records.first!.sns
let _: SNSBody = try message.payload()

// handle your message

return ctx.eventLoop.makeSucceededFuture(Void())
}
catch {
return ctx.eventLoop.makeFailedFuture(error)
}
}

func handleSQS(event: SQS.Event, ctx: Context) -> EventLoopFuture<Void> {
Expand Down Expand Up @@ -50,6 +63,12 @@ func handleAPIRequest(req: APIGateway.Request, ctx: Context) -> EventLoopFuture<
return ctx.eventLoop.makeSucceededFuture(response)
}

func handleS3(event: S3.Event, ctx: Context) -> EventLoopFuture<Void> {
ctx.logger.info("Payload: \(String(describing: event))")

return ctx.eventLoop.makeSucceededFuture(Void())
}

func handleLoadBalancerRequest(req: ALB.TargetGroupRequest, ctx: Context) ->
EventLoopFuture<ALB.TargetGroupResponse>
{
Expand Down Expand Up @@ -100,6 +119,8 @@ do {
handler = printOriginalPayload(LambdaRuntime.codable(handleCloudwatchSchedule))
case "api":
handler = printOriginalPayload(APIGateway.handler(handleAPIRequest))
case "s3":
handler = printOriginalPayload(LambdaRuntime.codable(handleS3))
case "loadbalancer":
handler = printOriginalPayload(ALB.handler(multiValueHeadersEnabled: true, handleLoadBalancerRequest))
default:
Expand Down
28 changes: 25 additions & 3 deletions Examples/EventSources/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,28 @@ Resources:
Properties:
Path: /{proxy+}
Method: ANY

# --- s3

S3TestEventBucket:
Type: "AWS::S3::Bucket"
Properties:
AccessControl: Private

HandleS3Event:
Type: AWS::Serverless::Function
Properties:
CodeUri: lambda.zip
Handler: "s3"
Runtime: provided
Layers:
- !Ref SwiftLayer
Events:
s3:
Type: S3
Properties:
Bucket: !Ref S3TestEventBucket
Events: s3:ObjectCreated:*

# --- load balancer

Expand Down Expand Up @@ -236,7 +258,7 @@ Resources:

TestLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Properties:
Scheme: internet-facing
Type: application
Subnets:
Expand All @@ -259,9 +281,9 @@ Resources:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
DependsOn:
- HandleLoadBalancerLambdaInvokePermission
Properties:
Properties:
Name: EinSternDerDeinenNamenTraegt
Targets:
Targets:
- Id: !GetAtt HandleLoadBalancerLambda.Arn
TargetGroupAttributes:
- Key: lambda.multi_value_headers.enabled
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Learn in depth how to build Lambdas in Swift in my blog series:

- [x] Built on top of `Swift-NIO`
- [x] Integration with Swift [`Logging`](https://github.com/apple/swift-log)
- [x] Ready-to-use [AWS Events](https://github.com/fabianfett/swift-lambda-runtime/tree/master/Sources/LambdaRuntime/Events) structs to get started as fast as possible. Currently implemented: Application Load Balancer, APIGateway, Cloudwatch Scheduled Events, DynamoDB Streams, SNS and SQS Messages. More coming soon.
- [x] Ready-to-use [AWS Events](https://github.com/fabianfett/swift-lambda-runtime/tree/master/Sources/LambdaRuntime/Events) structs to get started as fast as possible. Currently implemented: Application Load Balancer, APIGateway, Cloudwatch Scheduled Events, DynamoDB Streams, S3, SNS and SQS Messages. More coming soon.
- [x] [Tested integration](https://github.com/fabianfett/swift-lambda-runtime/blob/master/Examples/TodoAPIGateway/Sources/TodoAPIGateway/main.swift) with [`aws-swift-sdk`](https://github.com/swift-aws/aws-sdk-swift)
- [x] [Two examples](https://github.com/fabianfett/swift-lambda-runtime/tree/master/Examples) to get you up and running as fast as possible (including an [API-Gateway Todo-List](http://todobackend.com/client/index.html?https://mwpixnkbzj.execute-api.eu-central-1.amazonaws.com/test/todos))
- [x] Unit and end-to-end tests
Expand Down
108 changes: 108 additions & 0 deletions Sources/LambdaRuntime/Events/S3.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Foundation
import NIO

// https://github.com/aws/aws-lambda-go/blob/master/events/s3.go
public struct S3 {

public struct Event: Decodable {
public struct Record {
public let eventVersion: String
public let eventSource: String
public let awsRegion: String
public let eventTime: Date
public let eventName: String
public let userIdentity: UserIdentity
public let requestParameters: RequestParameters
public let responseElements: [String: String]
public let s3: Entity
}

public let records: [Record]

public enum CodingKeys: String, CodingKey {
case records = "Records"
}
}

public struct RequestParameters: Codable, Equatable {
public let sourceIPAddress: String
}

public struct UserIdentity: Codable, Equatable {
public let principalId: String
}

public struct Entity: Codable {
public let configurationId : String
public let schemaVersion : String
public let bucket : Bucket
public let object : Object

enum CodingKeys: String, CodingKey {
case configurationId = "configurationId"
case schemaVersion = "s3SchemaVersion"
case bucket = "bucket"
case object = "object"
}
}

public struct Bucket: Codable {
public let name : String
public let ownerIdentity: UserIdentity
public let arn : String
}

public struct Object: Codable {
public let key : String
public let size : UInt64
public let urlDecodedKey: String?
public let versionId : String?
public let eTag : String
public let sequencer : String
}
}

extension S3.Event.Record: Decodable {

enum CodingKeys: String, CodingKey {
case eventVersion
case eventSource
case awsRegion
case eventTime
case eventName
case userIdentity
case requestParameters
case responseElements
case s3
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.eventVersion = try container.decode(String.self, forKey: .eventVersion)
self.eventSource = try container.decode(String.self, forKey: .eventSource)
self.awsRegion = try container.decode(String.self, forKey: .awsRegion)

let dateString = try container.decode(String.self, forKey: .eventTime)
guard let timestamp = S3.Event.Record.dateFormatter.date(from: dateString) else {
let dateFormat = String(describing: S3.Event.Record.dateFormatter.dateFormat)
throw DecodingError.dataCorruptedError(forKey: .eventTime, in: container, debugDescription:
"Expected date to be in format `\(dateFormat)`, but `\(dateFormat) does not forfill format`")
}
self.eventTime = timestamp

self.eventName = try container.decode(String.self, forKey: .eventName)
self.userIdentity = try container.decode(S3.UserIdentity.self, forKey: .userIdentity)
self.requestParameters = try container.decode(S3.RequestParameters.self, forKey: .requestParameters)
self.responseElements = try container.decodeIfPresent([String:String].self, forKey: .responseElements) ?? [:]
self.s3 = try container.decode(S3.Entity.self, forKey: .s3)
}

private static let dateFormatter: DateFormatter = S3.Event.Record.createDateFormatter()
private static func createDateFormatter() -> DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}
}
84 changes: 84 additions & 0 deletions Tests/LambdaRuntimeTests/Events/S3Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Foundation
import XCTest
import NIO
@testable import LambdaRuntime

class S3Tests: XCTestCase {

static let eventPayload = """
{
"Records": [
{
"eventVersion":"2.1",
"eventSource":"aws:s3",
"awsRegion":"eu-central-1",
"eventTime":"2020-01-13T09:25:40.621Z",
"eventName":"ObjectCreated:Put",
"userIdentity":{
"principalId":"AWS:AAAAAAAJ2MQ4YFQZ7AULJ"
},
"requestParameters":{
"sourceIPAddress":"123.123.123.123"
},
"responseElements":{
"x-amz-request-id":"01AFA1430E18C358",
"x-amz-id-2":"JsbNw6sHGFwgzguQjbYcew//bfAeZITyTYLfjuu1U4QYqCq5CPlSyYLtvWQS+gw0RxcroItGwm8="
},
"s3":{
"s3SchemaVersion":"1.0",
"configurationId":"98b55bc4-3c0c-4007-b727-c6b77a259dde",
"bucket":{
"name":"eventsources",
"ownerIdentity":{
"principalId":"AAAAAAAAAAAAAA"
},
"arn":"arn:aws:s3:::eventsources"
},
"object":{
"key":"Hi.md",
"size":2880,
"eTag":"91a7f2c3ae81bcc6afef83979b463f0e",
"sequencer":"005E1C37948E783A6E"
}
}
}
]
}
"""

struct TestStruct: Decodable {
let hello: String
}

func testSimpleEventFromJSON() {
let data = S3Tests.eventPayload.data(using: .utf8)!
do {
let event = try JSONDecoder().decode(S3.Event.self, from: data)

guard let record = event.records.first else {
XCTFail("Expected to have one record"); return
}

XCTAssertEqual(record.eventVersion, "2.1")
XCTAssertEqual(record.eventSource, "aws:s3")
XCTAssertEqual(record.awsRegion, "eu-central-1")
XCTAssertEqual(record.eventName, "ObjectCreated:Put")
XCTAssertEqual(record.eventTime, Date(timeIntervalSince1970: 1578907540.621))
XCTAssertEqual(record.userIdentity, S3.UserIdentity(principalId: "AWS:AAAAAAAJ2MQ4YFQZ7AULJ"))
XCTAssertEqual(record.requestParameters, S3.RequestParameters(sourceIPAddress: "123.123.123.123"))
XCTAssertEqual(record.responseElements.count, 2)
XCTAssertEqual(record.s3.schemaVersion, "1.0")
XCTAssertEqual(record.s3.configurationId, "98b55bc4-3c0c-4007-b727-c6b77a259dde")
XCTAssertEqual(record.s3.bucket.name, "eventsources")
XCTAssertEqual(record.s3.bucket.ownerIdentity, S3.UserIdentity(principalId: "AAAAAAAAAAAAAA"))
XCTAssertEqual(record.s3.bucket.arn, "arn:aws:s3:::eventsources")
XCTAssertEqual(record.s3.object.key, "Hi.md")
XCTAssertEqual(record.s3.object.size, 2880)
XCTAssertEqual(record.s3.object.eTag, "91a7f2c3ae81bcc6afef83979b463f0e")
XCTAssertEqual(record.s3.object.sequencer, "005E1C37948E783A6E")
}
catch {
XCTFail("Unexpected error: \(error)")
}
}
}