diff --git a/Package.swift b/Package.swift index 67d12eb8..cdd1453a 100644 --- a/Package.swift +++ b/Package.swift @@ -4,8 +4,12 @@ import PackageDescription let package = Package( name: "swift-aws-lambda-runtime", + platforms: [ + .macOS(.v10_13), + ], products: [ .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), + .library(name: "AWSLambdaEvents", targets: ["AWSLambdaEvents"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"), @@ -20,6 +24,8 @@ let package = Package( .product(name: "NIOFoundationCompat", package: "swift-nio"), ]), .testTarget(name: "AWSLambdaRuntimeTests", dependencies: ["AWSLambdaRuntime"]), + .target(name: "AWSLambdaEvents", dependencies: []), + .testTarget(name: "AWSLambdaEventsTests", dependencies: ["AWSLambdaEvents"]), // samples .target(name: "StringSample", dependencies: ["AWSLambdaRuntime"]), .target(name: "CodableSample", dependencies: ["AWSLambdaRuntime"]), diff --git a/Sources/AWSLambdaEvents/DateWrappers.swift b/Sources/AWSLambdaEvents/DateWrappers.swift new file mode 100644 index 00000000..df9985fe --- /dev/null +++ b/Sources/AWSLambdaEvents/DateWrappers.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import struct Foundation.Date +import class Foundation.ISO8601DateFormatter + +@propertyWrapper +public struct ISO8601WithFractionalSecondsCoding: Decodable { + public let wrappedValue: Date + + public init(wrappedValue: Date) { + self.wrappedValue = wrappedValue + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + guard let date = Self.dateFormatter.date(from: dateString) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: + "Expected date to be in iso8601 date format with fractional seconds, but `\(dateString) does not forfill format`") + } + self.wrappedValue = date + } + + private static let dateFormatter: ISO8601DateFormatter = Self.createDateFormatter() + private static func createDateFormatter() -> ISO8601DateFormatter { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [ + .withInternetDateTime, + .withDashSeparatorInDate, + .withColonSeparatorInTime, + .withColonSeparatorInTimeZone, + .withFractionalSeconds, + ] + return formatter + } +} diff --git a/Sources/AWSLambdaEvents/S3.swift b/Sources/AWSLambdaEvents/S3.swift new file mode 100644 index 00000000..bc9c4f6d --- /dev/null +++ b/Sources/AWSLambdaEvents/S3.swift @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import struct Foundation.Date + +// https://github.com/aws/aws-lambda-go/blob/master/events/s3.go +public enum S3 { + public struct Event: Decodable { + public struct Record: Decodable { + public let eventVersion: String + public let eventSource: String + public let awsRegion: String + + @ISO8601WithFractionalSecondsCoding + public var 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 + case schemaVersion = "s3SchemaVersion" + case bucket + case 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 + } +} diff --git a/Tests/AWSLambdaEventsTests/S3Tests.swift b/Tests/AWSLambdaEventsTests/S3Tests.swift new file mode 100644 index 00000000..6f87c05d --- /dev/null +++ b/Tests/AWSLambdaEventsTests/S3Tests.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@testable import AWSLambdaEvents +import XCTest + +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" + } + } + } + ] + } + """ + + func testSimpleEventFromJSON() { + let data = S3Tests.eventPayload.data(using: .utf8)! + var event: S3.Event? + XCTAssertNoThrow(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: 1_578_907_540.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") + } +}