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

Commit 36bf21e

Browse files
authored
Added S3Event type. (#18)
Fixes (#14)
1 parent e5c4768 commit 36bf21e

File tree

5 files changed

+242
-7
lines changed

5 files changed

+242
-7
lines changed

Diff for: Examples/EventSources/Sources/EventSources/main.swift

+24-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,23 @@ let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
1010
defer { try! group.syncShutdownGracefully() }
1111
let logger = Logger(label: "AWSLambda.EventSources")
1212

13+
struct SNSBody: Codable {
14+
let name: String
15+
let whatevar: String
16+
}
17+
1318
func handleSNS(event: SNS.Event, ctx: Context) -> EventLoopFuture<Void> {
14-
ctx.logger.info("Payload: \(String(describing: event))")
15-
16-
return ctx.eventLoop.makeSucceededFuture(Void())
19+
do {
20+
let message = event.records.first!.sns
21+
let _: SNSBody = try message.payload()
22+
23+
// handle your message
24+
25+
return ctx.eventLoop.makeSucceededFuture(Void())
26+
}
27+
catch {
28+
return ctx.eventLoop.makeFailedFuture(error)
29+
}
1730
}
1831

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

66+
func handleS3(event: S3.Event, ctx: Context) -> EventLoopFuture<Void> {
67+
ctx.logger.info("Payload: \(String(describing: event))")
68+
69+
return ctx.eventLoop.makeSucceededFuture(Void())
70+
}
71+
5372
func handleLoadBalancerRequest(req: ALB.TargetGroupRequest, ctx: Context) ->
5473
EventLoopFuture<ALB.TargetGroupResponse>
5574
{
@@ -100,6 +119,8 @@ do {
100119
handler = printOriginalPayload(LambdaRuntime.codable(handleCloudwatchSchedule))
101120
case "api":
102121
handler = printOriginalPayload(APIGateway.handler(handleAPIRequest))
122+
case "s3":
123+
handler = printOriginalPayload(LambdaRuntime.codable(handleS3))
103124
case "loadbalancer":
104125
handler = printOriginalPayload(ALB.handler(multiValueHeadersEnabled: true, handleLoadBalancerRequest))
105126
default:

Diff for: Examples/EventSources/template.yaml

+25-3
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,28 @@ Resources:
197197
Properties:
198198
Path: /{proxy+}
199199
Method: ANY
200+
201+
# --- s3
202+
203+
S3TestEventBucket:
204+
Type: "AWS::S3::Bucket"
205+
Properties:
206+
AccessControl: Private
207+
208+
HandleS3Event:
209+
Type: AWS::Serverless::Function
210+
Properties:
211+
CodeUri: lambda.zip
212+
Handler: "s3"
213+
Runtime: provided
214+
Layers:
215+
- !Ref SwiftLayer
216+
Events:
217+
s3:
218+
Type: S3
219+
Properties:
220+
Bucket: !Ref S3TestEventBucket
221+
Events: s3:ObjectCreated:*
200222

201223
# --- load balancer
202224

@@ -236,7 +258,7 @@ Resources:
236258

237259
TestLoadBalancer:
238260
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
239-
Properties:
261+
Properties:
240262
Scheme: internet-facing
241263
Type: application
242264
Subnets:
@@ -259,9 +281,9 @@ Resources:
259281
Type: AWS::ElasticLoadBalancingV2::TargetGroup
260282
DependsOn:
261283
- HandleLoadBalancerLambdaInvokePermission
262-
Properties:
284+
Properties:
263285
Name: EinSternDerDeinenNamenTraegt
264-
Targets:
286+
Targets:
265287
- Id: !GetAtt HandleLoadBalancerLambda.Arn
266288
TargetGroupAttributes:
267289
- Key: lambda.multi_value_headers.enabled

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Learn in depth how to build Lambdas in Swift in my blog series:
5555

5656
- [x] Built on top of `Swift-NIO`
5757
- [x] Integration with Swift [`Logging`](https://github.com/apple/swift-log)
58-
- [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.
58+
- [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.
5959
- [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)
6060
- [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))
6161
- [x] Unit and end-to-end tests

Diff for: Sources/LambdaRuntime/Events/S3.swift

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import Foundation
2+
import NIO
3+
4+
// https://github.com/aws/aws-lambda-go/blob/master/events/s3.go
5+
public struct S3 {
6+
7+
public struct Event: Decodable {
8+
public struct Record {
9+
public let eventVersion: String
10+
public let eventSource: String
11+
public let awsRegion: String
12+
public let eventTime: Date
13+
public let eventName: String
14+
public let userIdentity: UserIdentity
15+
public let requestParameters: RequestParameters
16+
public let responseElements: [String: String]
17+
public let s3: Entity
18+
}
19+
20+
public let records: [Record]
21+
22+
public enum CodingKeys: String, CodingKey {
23+
case records = "Records"
24+
}
25+
}
26+
27+
public struct RequestParameters: Codable, Equatable {
28+
public let sourceIPAddress: String
29+
}
30+
31+
public struct UserIdentity: Codable, Equatable {
32+
public let principalId: String
33+
}
34+
35+
public struct Entity: Codable {
36+
public let configurationId : String
37+
public let schemaVersion : String
38+
public let bucket : Bucket
39+
public let object : Object
40+
41+
enum CodingKeys: String, CodingKey {
42+
case configurationId = "configurationId"
43+
case schemaVersion = "s3SchemaVersion"
44+
case bucket = "bucket"
45+
case object = "object"
46+
}
47+
}
48+
49+
public struct Bucket: Codable {
50+
public let name : String
51+
public let ownerIdentity: UserIdentity
52+
public let arn : String
53+
}
54+
55+
public struct Object: Codable {
56+
public let key : String
57+
public let size : UInt64
58+
public let urlDecodedKey: String?
59+
public let versionId : String?
60+
public let eTag : String
61+
public let sequencer : String
62+
}
63+
}
64+
65+
extension S3.Event.Record: Decodable {
66+
67+
enum CodingKeys: String, CodingKey {
68+
case eventVersion
69+
case eventSource
70+
case awsRegion
71+
case eventTime
72+
case eventName
73+
case userIdentity
74+
case requestParameters
75+
case responseElements
76+
case s3
77+
}
78+
79+
public init(from decoder: Decoder) throws {
80+
let container = try decoder.container(keyedBy: CodingKeys.self)
81+
82+
self.eventVersion = try container.decode(String.self, forKey: .eventVersion)
83+
self.eventSource = try container.decode(String.self, forKey: .eventSource)
84+
self.awsRegion = try container.decode(String.self, forKey: .awsRegion)
85+
86+
let dateString = try container.decode(String.self, forKey: .eventTime)
87+
guard let timestamp = S3.Event.Record.dateFormatter.date(from: dateString) else {
88+
let dateFormat = String(describing: S3.Event.Record.dateFormatter.dateFormat)
89+
throw DecodingError.dataCorruptedError(forKey: .eventTime, in: container, debugDescription:
90+
"Expected date to be in format `\(dateFormat)`, but `\(dateFormat) does not forfill format`")
91+
}
92+
self.eventTime = timestamp
93+
94+
self.eventName = try container.decode(String.self, forKey: .eventName)
95+
self.userIdentity = try container.decode(S3.UserIdentity.self, forKey: .userIdentity)
96+
self.requestParameters = try container.decode(S3.RequestParameters.self, forKey: .requestParameters)
97+
self.responseElements = try container.decodeIfPresent([String:String].self, forKey: .responseElements) ?? [:]
98+
self.s3 = try container.decode(S3.Entity.self, forKey: .s3)
99+
}
100+
101+
private static let dateFormatter: DateFormatter = S3.Event.Record.createDateFormatter()
102+
private static func createDateFormatter() -> DateFormatter {
103+
let formatter = DateFormatter()
104+
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
105+
formatter.timeZone = TimeZone(secondsFromGMT: 0)
106+
return formatter
107+
}
108+
}

Diff for: Tests/LambdaRuntimeTests/Events/S3Tests.swift

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import Foundation
2+
import XCTest
3+
import NIO
4+
@testable import LambdaRuntime
5+
6+
class S3Tests: XCTestCase {
7+
8+
static let eventPayload = """
9+
{
10+
"Records": [
11+
{
12+
"eventVersion":"2.1",
13+
"eventSource":"aws:s3",
14+
"awsRegion":"eu-central-1",
15+
"eventTime":"2020-01-13T09:25:40.621Z",
16+
"eventName":"ObjectCreated:Put",
17+
"userIdentity":{
18+
"principalId":"AWS:AAAAAAAJ2MQ4YFQZ7AULJ"
19+
},
20+
"requestParameters":{
21+
"sourceIPAddress":"123.123.123.123"
22+
},
23+
"responseElements":{
24+
"x-amz-request-id":"01AFA1430E18C358",
25+
"x-amz-id-2":"JsbNw6sHGFwgzguQjbYcew//bfAeZITyTYLfjuu1U4QYqCq5CPlSyYLtvWQS+gw0RxcroItGwm8="
26+
},
27+
"s3":{
28+
"s3SchemaVersion":"1.0",
29+
"configurationId":"98b55bc4-3c0c-4007-b727-c6b77a259dde",
30+
"bucket":{
31+
"name":"eventsources",
32+
"ownerIdentity":{
33+
"principalId":"AAAAAAAAAAAAAA"
34+
},
35+
"arn":"arn:aws:s3:::eventsources"
36+
},
37+
"object":{
38+
"key":"Hi.md",
39+
"size":2880,
40+
"eTag":"91a7f2c3ae81bcc6afef83979b463f0e",
41+
"sequencer":"005E1C37948E783A6E"
42+
}
43+
}
44+
}
45+
]
46+
}
47+
"""
48+
49+
struct TestStruct: Decodable {
50+
let hello: String
51+
}
52+
53+
func testSimpleEventFromJSON() {
54+
let data = S3Tests.eventPayload.data(using: .utf8)!
55+
do {
56+
let event = try JSONDecoder().decode(S3.Event.self, from: data)
57+
58+
guard let record = event.records.first else {
59+
XCTFail("Expected to have one record"); return
60+
}
61+
62+
XCTAssertEqual(record.eventVersion, "2.1")
63+
XCTAssertEqual(record.eventSource, "aws:s3")
64+
XCTAssertEqual(record.awsRegion, "eu-central-1")
65+
XCTAssertEqual(record.eventName, "ObjectCreated:Put")
66+
XCTAssertEqual(record.eventTime, Date(timeIntervalSince1970: 1578907540.621))
67+
XCTAssertEqual(record.userIdentity, S3.UserIdentity(principalId: "AWS:AAAAAAAJ2MQ4YFQZ7AULJ"))
68+
XCTAssertEqual(record.requestParameters, S3.RequestParameters(sourceIPAddress: "123.123.123.123"))
69+
XCTAssertEqual(record.responseElements.count, 2)
70+
XCTAssertEqual(record.s3.schemaVersion, "1.0")
71+
XCTAssertEqual(record.s3.configurationId, "98b55bc4-3c0c-4007-b727-c6b77a259dde")
72+
XCTAssertEqual(record.s3.bucket.name, "eventsources")
73+
XCTAssertEqual(record.s3.bucket.ownerIdentity, S3.UserIdentity(principalId: "AAAAAAAAAAAAAA"))
74+
XCTAssertEqual(record.s3.bucket.arn, "arn:aws:s3:::eventsources")
75+
XCTAssertEqual(record.s3.object.key, "Hi.md")
76+
XCTAssertEqual(record.s3.object.size, 2880)
77+
XCTAssertEqual(record.s3.object.eTag, "91a7f2c3ae81bcc6afef83979b463f0e")
78+
XCTAssertEqual(record.s3.object.sequencer, "005E1C37948E783A6E")
79+
}
80+
catch {
81+
XCTFail("Unexpected error: \(error)")
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)