diff --git a/Examples/LambdaFunctions/Package.swift b/Examples/LambdaFunctions/Package.swift index ae79d287..1c06c644 100644 --- a/Examples/LambdaFunctions/Package.swift +++ b/Examples/LambdaFunctions/Package.swift @@ -14,8 +14,6 @@ let package = Package( .executable(name: "Benchmark", targets: ["Benchmark"]), // demonstrate different types of error handling .executable(name: "ErrorHandling", targets: ["ErrorHandling"]), - // demostrate how to integrate with AWS API Gateway - .executable(name: "APIGateway", targets: ["APIGateway"]), // fully featured example with domain specific business logic .executable(name: "CurrencyExchange", targets: ["CurrencyExchange"]), ], @@ -35,10 +33,6 @@ let package = Package( .target(name: "ErrorHandling", dependencies: [ .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), ]), - .target(name: "APIGateway", dependencies: [ - .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), - .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-runtime"), - ]), .target(name: "CurrencyExchange", dependencies: [ .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), ]), diff --git a/Examples/LambdaFunctions/Sources/APIGateway/main.swift b/Examples/LambdaFunctions/Sources/APIGateway/main.swift deleted file mode 100644 index abe0f5a5..00000000 --- a/Examples/LambdaFunctions/Sources/APIGateway/main.swift +++ /dev/null @@ -1,34 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftAWSLambdaRuntime open source project -// -// Copyright (c) 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 AWSLambdaEvents -import AWSLambdaRuntime -import NIO - -// MARK: - Run Lambda - -Lambda.run(APIGatewayProxyLambda()) - -// MARK: - Handler, Request and Response - -// FIXME: Use proper Event abstractions once added to AWSLambdaRuntime -struct APIGatewayProxyLambda: EventLoopLambdaHandler { - public typealias In = APIGateway.V2.Request - public typealias Out = APIGateway.V2.Response - - public func handle(context: Lambda.Context, event: APIGateway.V2.Request) -> EventLoopFuture { - context.logger.debug("hello, api gateway!") - return context.eventLoop.makeSucceededFuture(APIGateway.V2.Response(statusCode: .ok, body: "hello, world!")) - } -} diff --git a/Package.swift b/Package.swift index ce0fad78..2f3488ea 100644 --- a/Package.swift +++ b/Package.swift @@ -9,8 +9,6 @@ let package = Package( .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), // this has all the main functionality for lambda and it does not link Foundation .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), - // common AWS events - .library(name: "AWSLambdaEvents", targets: ["AWSLambdaEvents"]), // for testing only .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], @@ -41,8 +39,6 @@ let package = Package( .byName(name: "AWSLambdaRuntimeCore"), .byName(name: "AWSLambdaRuntime"), ]), - .target(name: "AWSLambdaEvents", dependencies: []), - .testTarget(name: "AWSLambdaEventsTests", dependencies: ["AWSLambdaEvents"]), // testing helper .target(name: "AWSLambdaTesting", dependencies: [ .byName(name: "AWSLambdaRuntime"), diff --git a/Sources/AWSLambdaEvents/ALB.swift b/Sources/AWSLambdaEvents/ALB.swift deleted file mode 100644 index a5495399..00000000 --- a/Sources/AWSLambdaEvents/ALB.swift +++ /dev/null @@ -1,77 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 class Foundation.JSONEncoder - -// https://github.com/aws/aws-lambda-go/blob/master/events/alb.go -public enum ALB { - /// ALBTargetGroupRequest contains data originating from the ALB Lambda target group integration - public struct TargetGroupRequest: Codable { - /// ALBTargetGroupRequestContext contains the information to identify the load balancer invoking the lambda - public struct Context: Codable { - public let elb: ELBContext - } - - public let httpMethod: HTTPMethod - public let path: String - public let queryStringParameters: [String: String] - - /// Depending on your configuration of your target group either `headers` or `multiValueHeaders` - /// are set. - /// - /// For more information visit: - /// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers - public let headers: HTTPHeaders? - - /// Depending on your configuration of your target group either `headers` or `multiValueHeaders` - /// are set. - /// - /// For more information visit: - /// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers - public let multiValueHeaders: HTTPMultiValueHeaders? - public let requestContext: Context - public let isBase64Encoded: Bool - public let body: String? - } - - /// ELBContext contains the information to identify the ARN invoking the lambda - public struct ELBContext: Codable { - public let targetGroupArn: String - } - - public struct TargetGroupResponse: Codable { - public var statusCode: HTTPResponseStatus - public var statusDescription: String? - public var headers: HTTPHeaders? - public var multiValueHeaders: HTTPMultiValueHeaders? - public var body: String - public var isBase64Encoded: Bool - - public init( - statusCode: HTTPResponseStatus, - statusDescription: String? = nil, - headers: HTTPHeaders? = nil, - multiValueHeaders: HTTPMultiValueHeaders? = nil, - body: String = "", - isBase64Encoded: Bool = false - ) { - self.statusCode = statusCode - self.statusDescription = statusDescription - self.headers = headers - self.multiValueHeaders = multiValueHeaders - self.body = body - self.isBase64Encoded = isBase64Encoded - } - } -} diff --git a/Sources/AWSLambdaEvents/APIGateway+V2.swift b/Sources/AWSLambdaEvents/APIGateway+V2.swift deleted file mode 100644 index 1f4f0e22..00000000 --- a/Sources/AWSLambdaEvents/APIGateway+V2.swift +++ /dev/null @@ -1,116 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -extension APIGateway { - public struct V2 {} -} - -extension APIGateway.V2 { - /// APIGateway.V2.Request contains data coming from the new HTTP API Gateway - public struct Request: Codable { - /// Context contains the information to identify the AWS account and resources invoking the Lambda function. - public struct Context: Codable { - public struct HTTP: Codable { - public let method: HTTPMethod - public let path: String - public let `protocol`: String - public let sourceIp: String - public let userAgent: String - } - - /// Authorizer contains authorizer information for the request context. - public struct Authorizer: Codable { - /// JWT contains JWT authorizer information for the request context. - public struct JWT: Codable { - public let claims: [String: String] - public let scopes: [String]? - } - - public let jwt: JWT - } - - public let accountId: String - public let apiId: String - public let domainName: String - public let domainPrefix: String - public let stage: String - public let requestId: String - - public let http: HTTP - public let authorizer: Authorizer? - - /// The request time in format: 23/Apr/2020:11:08:18 +0000 - public let time: String - public let timeEpoch: UInt64 - } - - public let version: String - public let routeKey: String - public let rawPath: String - public let rawQueryString: String - - public let cookies: [String]? - public let headers: HTTPHeaders - public let queryStringParameters: [String: String]? - public let pathParameters: [String: String]? - - public let context: Context - public let stageVariables: [String: String]? - - public let body: String? - public let isBase64Encoded: Bool - - enum CodingKeys: String, CodingKey { - case version - case routeKey - case rawPath - case rawQueryString - - case cookies - case headers - case queryStringParameters - case pathParameters - - case context = "requestContext" - case stageVariables - - case body - case isBase64Encoded - } - } -} - -extension APIGateway.V2 { - public struct Response: Codable { - public var statusCode: HTTPResponseStatus - public var headers: HTTPHeaders? - public var body: String? - public var isBase64Encoded: Bool? - public var cookies: [String]? - - public init( - statusCode: HTTPResponseStatus, - headers: HTTPHeaders? = nil, - body: String? = nil, - isBase64Encoded: Bool? = nil, - cookies: [String]? = nil - ) { - self.statusCode = statusCode - self.headers = headers - self.body = body - self.isBase64Encoded = isBase64Encoded - self.cookies = cookies - } - } -} diff --git a/Sources/AWSLambdaEvents/APIGateway.swift b/Sources/AWSLambdaEvents/APIGateway.swift deleted file mode 100644 index a944cef2..00000000 --- a/Sources/AWSLambdaEvents/APIGateway.swift +++ /dev/null @@ -1,93 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 class Foundation.JSONEncoder - -// https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html -// https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html - -public enum APIGateway { - /// APIGatewayRequest contains data coming from the API Gateway - public struct Request: Codable { - public struct Context: Codable { - public struct Identity: Codable { - public let cognitoIdentityPoolId: String? - - public let apiKey: String? - public let userArn: String? - public let cognitoAuthenticationType: String? - public let caller: String? - public let userAgent: String? - public let user: String? - - public let cognitoAuthenticationProvider: String? - public let sourceIp: String? - public let accountId: String? - } - - public let resourceId: String - public let apiId: String - public let resourcePath: String - public let httpMethod: String - public let requestId: String - public let accountId: String - public let stage: String - - public let identity: Identity - public let extendedRequestId: String? - public let path: String - } - - public let resource: String - public let path: String - public let httpMethod: HTTPMethod - - public let queryStringParameters: [String: String]? - public let multiValueQueryStringParameters: [String: [String]]? - public let headers: HTTPHeaders - public let multiValueHeaders: HTTPMultiValueHeaders - public let pathParameters: [String: String]? - public let stageVariables: [String: String]? - - public let requestContext: Context - public let body: String? - public let isBase64Encoded: Bool - } -} - -// MARK: - Response - - -extension APIGateway { - public struct Response: Codable { - public var statusCode: HTTPResponseStatus - public var headers: HTTPHeaders? - public var multiValueHeaders: HTTPMultiValueHeaders? - public var body: String? - public var isBase64Encoded: Bool? - - public init( - statusCode: HTTPResponseStatus, - headers: HTTPHeaders? = nil, - multiValueHeaders: HTTPMultiValueHeaders? = nil, - body: String? = nil, - isBase64Encoded: Bool? = nil - ) { - self.statusCode = statusCode - self.headers = headers - self.multiValueHeaders = multiValueHeaders - self.body = body - self.isBase64Encoded = isBase64Encoded - } - } -} diff --git a/Sources/AWSLambdaEvents/AWSRegion.swift b/Sources/AWSLambdaEvents/AWSRegion.swift deleted file mode 100644 index e884d3a6..00000000 --- a/Sources/AWSLambdaEvents/AWSRegion.swift +++ /dev/null @@ -1,92 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -// list all available regions using aws cli: -// $ aws ssm get-parameters-by-path --path /aws/service/global-infrastructure/services/lambda/regions --output json - -/// Enumeration of the AWS Regions. -public struct AWSRegion: RawRepresentable, Equatable { - public typealias RawValue = String - - public let rawValue: String - - public init?(rawValue: String) { - self.rawValue = rawValue - } - - static var all: [AWSRegion] = [ - Self.ap_northeast_1, - Self.ap_northeast_2, - Self.ap_east_1, - Self.ap_southeast_1, - Self.ap_southeast_2, - Self.ap_south_1, - Self.cn_north_1, - Self.cn_northwest_1, - Self.eu_north_1, - Self.eu_west_1, - Self.eu_west_2, - Self.eu_west_3, - Self.eu_central_1, - Self.us_east_1, - Self.us_east_2, - Self.us_west_1, - Self.us_west_2, - Self.us_gov_east_1, - Self.us_gov_west_1, - Self.ca_central_1, - Self.sa_east_1, - Self.me_south_1, - ] - - public static var ap_northeast_1: Self { AWSRegion(rawValue: "ap-northeast-1")! } - public static var ap_northeast_2: Self { AWSRegion(rawValue: "ap-northeast-2")! } - public static var ap_east_1: Self { AWSRegion(rawValue: "ap-east-1")! } - public static var ap_southeast_1: Self { AWSRegion(rawValue: "ap-southeast-1")! } - public static var ap_southeast_2: Self { AWSRegion(rawValue: "ap-southeast-2")! } - public static var ap_south_1: Self { AWSRegion(rawValue: "ap-south-1")! } - - public static var cn_north_1: Self { AWSRegion(rawValue: "cn-north-1")! } - public static var cn_northwest_1: Self { AWSRegion(rawValue: "cn-northwest-1")! } - - public static var eu_north_1: Self { AWSRegion(rawValue: "eu-north-1")! } - public static var eu_west_1: Self { AWSRegion(rawValue: "eu-west-1")! } - public static var eu_west_2: Self { AWSRegion(rawValue: "eu-west-2")! } - public static var eu_west_3: Self { AWSRegion(rawValue: "eu-west-3")! } - public static var eu_central_1: Self { AWSRegion(rawValue: "eu-central-1")! } - - public static var us_east_1: Self { AWSRegion(rawValue: "us-east-1")! } - public static var us_east_2: Self { AWSRegion(rawValue: "us-east-2")! } - public static var us_west_1: Self { AWSRegion(rawValue: "us-west-1")! } - public static var us_west_2: Self { AWSRegion(rawValue: "us-west-2")! } - public static var us_gov_east_1: Self { AWSRegion(rawValue: "us-gov-east-1")! } - public static var us_gov_west_1: Self { AWSRegion(rawValue: "us-gov-west-1")! } - - public static var ca_central_1: Self { AWSRegion(rawValue: "ca-central-1")! } - public static var sa_east_1: Self { AWSRegion(rawValue: "sa-east-1")! } - public static var me_south_1: Self { AWSRegion(rawValue: "me-south-1")! } -} - -extension AWSRegion: Codable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let region = try container.decode(String.self) - self.init(rawValue: region)! - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self.rawValue) - } -} diff --git a/Sources/AWSLambdaEvents/AppSync.swift b/Sources/AWSLambdaEvents/AppSync.swift deleted file mode 100644 index 909cb337..00000000 --- a/Sources/AWSLambdaEvents/AppSync.swift +++ /dev/null @@ -1,169 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -// https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html -public enum AppSync { - public struct Event: Decodable { - public let arguments: [String: ArgumentValue] - - public enum ArgumentValue: Codable { - case string(String) - case dictionary([String: String]) - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if let strValue = try? container.decode(String.self) { - self = .string(strValue) - } else if let dictionaryValue = try? container.decode([String: String].self) { - self = .dictionary(dictionaryValue) - } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: """ - Unexpected AppSync argument. - Expected a String or a Dictionary. - """) - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .dictionary(let array): - try container.encode(array) - case .string(let str): - try container.encode(str) - } - } - } - - public let request: Request - public struct Request: Decodable { - let headers: HTTPHeaders - } - - public let source: [String: String]? - public let stash: [String: String]? - - public let info: Info - public struct Info: Codable { - public var selectionSetList: [String] - public var selectionSetGraphQL: String - public var parentTypeName: String - public var fieldName: String - public var variables: [String: String] - } - - public let identity: Identity? - public enum Identity: Codable { - case iam(IAMIdentity) - case cognitoUserPools(CognitoUserPoolIdentity) - - public struct IAMIdentity: Codable { - public let accountId: String - public let cognitoIdentityPoolId: String - public let cognitoIdentityId: String - public let sourceIp: [String] - public let username: String? - public let userArn: String - public let cognitoIdentityAuthType: String - public let cognitoIdentityAuthProvider: String - } - - public struct CognitoUserPoolIdentity: Codable { - public let defaultAuthStrategy: String - public let issuer: String - public let sourceIp: [String] - public let sub: String - public let username: String? - - public struct Claims { - let sub: String - let emailVerified: Bool - let iss: String - let phoneNumberVerified: Bool - let cognitoUsername: String - let aud: String - let eventId: String - let tokenUse: String - let authTime: Int - let phoneNumber: String? - let exp: Int - let iat: Int - let email: String? - - enum CodingKeys: String, CodingKey { - case sub - case emailVerified = "email_verified" - case iss - case phoneNumberVerified = "phone_number_verified" - case cognitoUsername = "cognito:username" - case aud - case eventId = "event_id" - case tokenUse = "token_use" - case authTime = "auth_time" - case phoneNumber = "phone_number" - case exp - case iat - case email - } - } - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if let iamIdentity = try? container.decode(IAMIdentity.self) { - self = .iam(iamIdentity) - } else if let cognitoIdentity = try? container.decode(CognitoUserPoolIdentity.self) { - self = .cognitoUserPools(cognitoIdentity) - } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: """ - Unexpected Identity argument. - Expected a IAM Identity or a Cognito User Pool Identity. - """) - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .iam(let iamIdentity): - try container.encode(iamIdentity) - case .cognitoUserPools(let cognitoUserPool): - try container.encode(cognitoUserPool) - } - } - } - } -} - -extension AppSync { - public enum Response: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .array(let array): - try container.encode(array) - case .object(let object): - try container.encode(object) - case .dictionary(let dictionary): - try container.encode(dictionary) - } - } - - case object(ResultType) - case array([ResultType]) - case dictionary([String: ResultType]) - } - - public typealias JSONStringResponse = Response -} diff --git a/Sources/AWSLambdaEvents/Cloudwatch.swift b/Sources/AWSLambdaEvents/Cloudwatch.swift deleted file mode 100644 index 4a1c59f6..00000000 --- a/Sources/AWSLambdaEvents/Cloudwatch.swift +++ /dev/null @@ -1,129 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -/// EventBridge has the same events/notification types as CloudWatch -typealias EventBridge = Cloudwatch - -public protocol CloudwatchDetail: Decodable { - static var name: String { get } -} - -extension CloudwatchDetail { - public var detailType: String { - Self.name - } -} - -public enum Cloudwatch { - /// CloudWatch.Event is the outer structure of an event sent via CloudWatch Events. - /// - /// **NOTE**: For examples of events that come via CloudWatch Events, see - /// https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html - /// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html - /// https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html - public struct Event: Decodable { - public let id: String - public let source: String - public let accountId: String - public let time: Date - public let region: AWSRegion - public let resources: [String] - public let detail: Detail - - enum CodingKeys: String, CodingKey { - case id - case source - case accountId = "account" - case time - case region - case resources - case detailType = "detail-type" - case detail - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.id = try container.decode(String.self, forKey: .id) - self.source = try container.decode(String.self, forKey: .source) - self.accountId = try container.decode(String.self, forKey: .accountId) - self.time = (try container.decode(ISO8601Coding.self, forKey: .time)).wrappedValue - self.region = try container.decode(AWSRegion.self, forKey: .region) - self.resources = try container.decode([String].self, forKey: .resources) - - let detailType = try container.decode(String.self, forKey: .detailType) - guard detailType.lowercased() == Detail.name.lowercased() else { - throw DetailTypeMismatch(name: detailType, type: Detail.self) - } - - self.detail = try container.decode(Detail.self, forKey: .detail) - } - } - - // MARK: - Common Event Types - - public typealias ScheduledEvent = Event - public struct Scheduled: CloudwatchDetail { - public static let name = "Scheduled Event" - } - - public enum EC2 { - public typealias InstanceStateChangeNotificationEvent = Event - public struct InstanceStateChangeNotification: CloudwatchDetail { - public static let name = "EC2 Instance State-change Notification" - - public enum State: String, Codable { - case running - case shuttingDown = "shutting-down" - case stopped - case stopping - case terminated - } - - public let instanceId: String - public let state: State - - enum CodingKeys: String, CodingKey { - case instanceId = "instance-id" - case state - } - } - - public typealias SpotInstanceInterruptionNoticeEvent = Event - public struct SpotInstanceInterruptionNotice: CloudwatchDetail { - public static let name = "EC2 Spot Instance Interruption Warning" - - public enum Action: String, Codable { - case hibernate - case stop - case terminate - } - - public let instanceId: String - public let action: Action - - enum CodingKeys: String, CodingKey { - case instanceId = "instance-id" - case action = "instance-action" - } - } - } - - struct DetailTypeMismatch: Error { - let name: String - let type: Any - } -} diff --git a/Sources/AWSLambdaEvents/DynamoDB.swift b/Sources/AWSLambdaEvents/DynamoDB.swift deleted file mode 100644 index f75a1135..00000000 --- a/Sources/AWSLambdaEvents/DynamoDB.swift +++ /dev/null @@ -1,934 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html -public enum DynamoDB { - public struct Event: Decodable { - public let records: [EventRecord] - - public enum CodingKeys: String, CodingKey { - case records = "Records" - } - } - - public enum KeyType: String, Codable { - case hash = "HASH" - case range = "RANGE" - } - - public enum OperationType: String, Codable { - case insert = "INSERT" - case modify = "MODIFY" - case remove = "REMOVE" - } - - public enum SharedIteratorType: String, Codable { - case trimHorizon = "TRIM_HORIZON" - case latest = "LATEST" - case atSequenceNumber = "AT_SEQUENCE_NUMBER" - case afterSequenceNumber = "AFTER_SEQUENCE_NUMBER" - } - - public enum StreamStatus: String, Codable { - case enabling = "ENABLING" - case enabled = "ENABLED" - case disabling = "DISABLING" - case disabled = "DISABLED" - } - - public enum StreamViewType: String, Codable { - /// the entire item, as it appeared after it was modified. - case newImage = "NEW_IMAGE" - /// the entire item, as it appeared before it was modified. - case oldImage = "OLD_IMAGE" - /// both the new and the old item images of the item. - case newAndOldImages = "NEW_AND_OLD_IMAGES" - /// only the key attributes of the modified item. - case keysOnly = "KEYS_ONLY" - } - - public struct EventRecord: Decodable { - /// The region in which the GetRecords request was received. - public let awsRegion: AWSRegion - - /// The main body of the stream record, containing all of the DynamoDB-specific - /// fields. - public let change: StreamRecord - - /// A globally unique identifier for the event that was recorded in this stream - /// record. - public let eventId: String - - /// The type of data modification that was performed on the DynamoDB table: - /// * INSERT - a new item was added to the table. - /// * MODIFY - one or more of an existing item's attributes were modified. - /// * REMOVE - the item was deleted from the table - public let eventName: OperationType - - /// The AWS service from which the stream record originated. For DynamoDB Streams, - /// this is aws:dynamodb. - public let eventSource: String - - /// The version number of the stream record format. This number is updated whenever - /// the structure of Record is modified. - /// - /// Client applications must not assume that eventVersion will remain at a particular - /// value, as this number is subject to change at any time. In general, eventVersion - /// will only increase as the low-level DynamoDB Streams API evolves. - public let eventVersion: String - - /// The event source ARN of DynamoDB - public let eventSourceArn: String - - /// Items that are deleted by the Time to Live process after expiration have - /// the following fields: - /// * Records[].userIdentity.type - /// - /// "Service" - /// * Records[].userIdentity.principalId - /// - /// "dynamodb.amazonaws.com" - public let userIdentity: UserIdentity? - - public enum CodingKeys: String, CodingKey { - case awsRegion - case change = "dynamodb" - case eventId = "eventID" - case eventName - case eventSource - case eventVersion - case eventSourceArn = "eventSourceARN" - case userIdentity - } - } - - public struct StreamRecord { - /// The approximate date and time when the stream record was created, in UNIX - /// epoch time (http://www.epochconverter.com/) format. - public let approximateCreationDateTime: Date? - - /// The primary key attribute(s) for the DynamoDB item that was modified. - public let keys: [String: AttributeValue] - - /// The item in the DynamoDB table as it appeared after it was modified. - public let newImage: [String: AttributeValue]? - - /// The item in the DynamoDB table as it appeared before it was modified. - public let oldImage: [String: AttributeValue]? - - /// The sequence number of the stream record. - public let sequenceNumber: String - - /// The size of the stream record, in bytes. - public let sizeBytes: Int64 - - /// The type of data from the modified DynamoDB item that was captured in this - /// stream record. - public let streamViewType: StreamViewType - } - - public struct UserIdentity: Codable { - public let type: String - public let principalId: String - } -} - -extension DynamoDB.StreamRecord: Decodable { - enum CodingKeys: String, CodingKey { - case approximateCreationDateTime = "ApproximateCreationDateTime" - case keys = "Keys" - case newImage = "NewImage" - case oldImage = "OldImage" - case sequenceNumber = "SequenceNumber" - case sizeBytes = "SizeBytes" - case streamViewType = "StreamViewType" - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.keys = try container.decode( - [String: DynamoDB.AttributeValue].self, - forKey: .keys - ) - - self.newImage = try container.decodeIfPresent( - [String: DynamoDB.AttributeValue].self, - forKey: .newImage - ) - self.oldImage = try container.decodeIfPresent( - [String: DynamoDB.AttributeValue].self, - forKey: .oldImage - ) - - self.sequenceNumber = try container.decode(String.self, forKey: .sequenceNumber) - self.sizeBytes = try container.decode(Int64.self, forKey: .sizeBytes) - self.streamViewType = try container.decode(DynamoDB.StreamViewType.self, forKey: .streamViewType) - - if let timestamp = try container.decodeIfPresent(Double.self, forKey: .approximateCreationDateTime) { - self.approximateCreationDateTime = Date(timeIntervalSince1970: timestamp) - } else { - self.approximateCreationDateTime = nil - } - } -} - -// MARK: - AttributeValue - - -extension DynamoDB { - public enum AttributeValue { - case boolean(Bool) - case binary([UInt8]) - case binarySet([[UInt8]]) - case string(String) - case stringSet([String]) - case null - case number(String) - case numberSet([String]) - - case list([AttributeValue]) - case map([String: AttributeValue]) - } -} - -extension DynamoDB.AttributeValue: Decodable { - enum CodingKeys: String, CodingKey { - case binary = "B" - case bool = "BOOL" - case binarySet = "BS" - case list = "L" - case map = "M" - case number = "N" - case numberSet = "NS" - case null = "NULL" - case string = "S" - case stringSet = "SS" - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - guard container.allKeys.count == 1, let key = container.allKeys.first else { - let context = DecodingError.Context( - codingPath: container.codingPath, - debugDescription: "Expected exactly one key, but got \(container.allKeys.count)" - ) - throw DecodingError.dataCorrupted(context) - } - - switch key { - case .binary: - let encoded = try container.decode(String.self, forKey: .binary) - self = .binary(try encoded.base64decoded()) - - case .bool: - let value = try container.decode(Bool.self, forKey: .bool) - self = .boolean(value) - - case .binarySet: - let values = try container.decode([String].self, forKey: .binarySet) - let buffers = try values.map { try $0.base64decoded() } - self = .binarySet(buffers) - - case .list: - let values = try container.decode([DynamoDB.AttributeValue].self, forKey: .list) - self = .list(values) - - case .map: - let value = try container.decode([String: DynamoDB.AttributeValue].self, forKey: .map) - self = .map(value) - - case .number: - let value = try container.decode(String.self, forKey: .number) - self = .number(value) - - case .numberSet: - let values = try container.decode([String].self, forKey: .numberSet) - self = .numberSet(values) - - case .null: - self = .null - - case .string: - let value = try container.decode(String.self, forKey: .string) - self = .string(value) - - case .stringSet: - let values = try container.decode([String].self, forKey: .stringSet) - self = .stringSet(values) - } - } -} - -extension DynamoDB.AttributeValue: Equatable { - public static func == (lhs: Self, rhs: Self) -> Bool { - switch (lhs, rhs) { - case (.boolean(let lhs), .boolean(let rhs)): - return lhs == rhs - case (.binary(let lhs), .binary(let rhs)): - return lhs == rhs - case (.binarySet(let lhs), .binarySet(let rhs)): - return lhs == rhs - case (.string(let lhs), .string(let rhs)): - return lhs == rhs - case (.stringSet(let lhs), .stringSet(let rhs)): - return lhs == rhs - case (.null, .null): - return true - case (.number(let lhs), .number(let rhs)): - return lhs == rhs - case (.numberSet(let lhs), .numberSet(let rhs)): - return lhs == rhs - case (.list(let lhs), .list(let rhs)): - return lhs == rhs - case (.map(let lhs), .map(let rhs)): - return lhs == rhs - default: - return false - } - } -} - -// MARK: DynamoDB AttributeValue Decoding - -extension DynamoDB { - public struct Decoder { - @usableFromInline var userInfo: [CodingUserInfoKey: Any] = [:] - - public init() {} - - @inlinable public func decode(_ type: T.Type, from image: [String: AttributeValue]) - throws -> T { - try self.decode(type, from: .map(image)) - } - - @inlinable public func decode(_ type: T.Type, from value: AttributeValue) - throws -> T { - let decoder = _DecoderImpl(userInfo: userInfo, from: value, codingPath: []) - return try decoder.decode(T.self) - } - } - - @usableFromInline internal struct _DecoderImpl: Swift.Decoder { - @usableFromInline let codingPath: [CodingKey] - @usableFromInline let userInfo: [CodingUserInfoKey: Any] - - @usableFromInline let value: AttributeValue - - @inlinable init(userInfo: [CodingUserInfoKey: Any], from value: AttributeValue, codingPath: [CodingKey]) { - self.userInfo = userInfo - self.codingPath = codingPath - self.value = value - } - - @inlinable public func decode(_: T.Type) throws -> T { - try T(from: self) - } - - @usableFromInline func container(keyedBy type: Key.Type) throws -> - KeyedDecodingContainer where Key: CodingKey { - guard case .map(let dictionary) = self.value else { - throw DecodingError.typeMismatch([String: AttributeValue].self, DecodingError.Context( - codingPath: self.codingPath, - debugDescription: "Expected to decode \([String: AttributeValue].self) but found \(self.value.debugDataTypeDescription) instead." - )) - } - - let container = _KeyedDecodingContainer( - impl: self, - codingPath: self.codingPath, - dictionary: dictionary - ) - return KeyedDecodingContainer(container) - } - - @usableFromInline func unkeyedContainer() throws -> UnkeyedDecodingContainer { - guard case .list(let array) = self.value else { - throw DecodingError.typeMismatch([AttributeValue].self, DecodingError.Context( - codingPath: self.codingPath, - debugDescription: "Expected to decode \([AttributeValue].self) but found \(self.value.debugDataTypeDescription) instead." - )) - } - - return _UnkeyedDecodingContainer( - impl: self, - codingPath: self.codingPath, - array: array - ) - } - - @usableFromInline func singleValueContainer() throws -> SingleValueDecodingContainer { - _SingleValueDecodingContainter( - impl: self, - codingPath: self.codingPath, - value: self.value - ) - } - } - - struct ArrayKey: CodingKey, Equatable { - init(index: Int) { - self.intValue = index - } - - init?(stringValue _: String) { - preconditionFailure("Did not expect to be initialized with a string") - } - - init?(intValue: Int) { - self.intValue = intValue - } - - var intValue: Int? - - var stringValue: String { - "Index \(self.intValue!)" - } - - static func == (lhs: ArrayKey, rhs: ArrayKey) -> Bool { - precondition(lhs.intValue != nil) - precondition(rhs.intValue != nil) - return lhs.intValue == rhs.intValue - } - } - - struct _KeyedDecodingContainer: KeyedDecodingContainerProtocol { - typealias Key = K - - let impl: _DecoderImpl - let codingPath: [CodingKey] - let dictionary: [String: AttributeValue] - - init(impl: _DecoderImpl, codingPath: [CodingKey], dictionary: [String: AttributeValue]) { - self.impl = impl - self.codingPath = codingPath - self.dictionary = dictionary - } - - var allKeys: [K] { - self.dictionary.keys.compactMap { K(stringValue: $0) } - } - - func contains(_ key: K) -> Bool { - if let _ = self.dictionary[key.stringValue] { - return true - } - return false - } - - func decodeNil(forKey key: K) throws -> Bool { - let value = try getValue(forKey: key) - return value == .null - } - - func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { - let value = try getValue(forKey: key) - - guard case .boolean(let bool) = value else { - throw self.createTypeMismatchError(type: type, forKey: key, value: value) - } - - return bool - } - - func decode(_ type: String.Type, forKey key: K) throws -> String { - let value = try getValue(forKey: key) - - guard case .string(let string) = value else { - throw self.createTypeMismatchError(type: type, forKey: key, value: value) - } - - return string - } - - func decode(_ type: Double.Type, forKey key: K) throws -> Double { - try self.decodeLosslessStringConvertible(key: key) - } - - func decode(_ type: Float.Type, forKey key: K) throws -> Float { - try self.decodeLosslessStringConvertible(key: key) - } - - func decode(_ type: Int.Type, forKey key: K) throws -> Int { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { - try self.decodeFixedWidthInteger(key: key) - } - - func decode(_ type: T.Type, forKey key: K) throws -> T where T: Decodable { - let decoder = try self.decoderForKey(key) - return try T(from: decoder) - } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws - -> KeyedDecodingContainer where NestedKey: CodingKey { - try self.decoderForKey(key).container(keyedBy: type) - } - - func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { - try self.decoderForKey(key).unkeyedContainer() - } - - func superDecoder() throws -> Swift.Decoder { - self.impl - } - - func superDecoder(forKey key: K) throws -> Swift.Decoder { - self.impl - } - - private func decoderForKey(_ key: K) throws -> _DecoderImpl { - let value = try getValue(forKey: key) - var newPath = self.codingPath - newPath.append(key) - - return _DecoderImpl( - userInfo: self.impl.userInfo, - from: value, - codingPath: newPath - ) - } - - @inline(__always) private func getValue(forKey key: K) throws -> AttributeValue { - guard let value = self.dictionary[key.stringValue] else { - throw DecodingError.keyNotFound(key, .init( - codingPath: self.codingPath, - debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\")." - )) - } - - return value - } - - @inline(__always) private func createTypeMismatchError(type: Any.Type, forKey key: K, value: AttributeValue) -> DecodingError { - let codingPath = self.codingPath + [key] - return DecodingError.typeMismatch(type, .init( - codingPath: codingPath, debugDescription: "Expected to decode \(type) but found \(value.debugDataTypeDescription) instead." - )) - } - - @inline(__always) private func decodeFixedWidthInteger(key: Self.Key) - throws -> T { - let value = try getValue(forKey: key) - - guard case .number(let number) = value else { - throw self.createTypeMismatchError(type: T.self, forKey: key, value: value) - } - - guard let integer = T(number) else { - throw DecodingError.dataCorruptedError( - forKey: key, - in: self, - debugDescription: "Parsed JSON number <\(number)> does not fit in \(T.self)." - ) - } - - return integer - } - - @inline(__always) private func decodeLosslessStringConvertible( - key: Self.Key) throws -> T { - let value = try getValue(forKey: key) - - guard case .number(let number) = value else { - throw self.createTypeMismatchError(type: T.self, forKey: key, value: value) - } - - guard let floatingPoint = T(number) else { - throw DecodingError.dataCorruptedError( - forKey: key, - in: self, - debugDescription: "Parsed JSON number <\(number)> does not fit in \(T.self)." - ) - } - - return floatingPoint - } - } - - struct _SingleValueDecodingContainter: SingleValueDecodingContainer { - let impl: _DecoderImpl - let value: AttributeValue - let codingPath: [CodingKey] - - init(impl: _DecoderImpl, codingPath: [CodingKey], value: AttributeValue) { - self.impl = impl - self.codingPath = codingPath - self.value = value - } - - func decodeNil() -> Bool { - self.value == .null - } - - func decode(_: Bool.Type) throws -> Bool { - guard case .boolean(let bool) = self.value else { - throw self.createTypeMismatchError(type: Bool.self, value: self.value) - } - - return bool - } - - func decode(_: String.Type) throws -> String { - guard case .string(let string) = self.value else { - throw self.createTypeMismatchError(type: String.self, value: self.value) - } - - return string - } - - func decode(_: Double.Type) throws -> Double { - try self.decodeLosslessStringConvertible() - } - - func decode(_: Float.Type) throws -> Float { - try self.decodeLosslessStringConvertible() - } - - func decode(_: Int.Type) throws -> Int { - try self.decodeFixedWidthInteger() - } - - func decode(_: Int8.Type) throws -> Int8 { - try self.decodeFixedWidthInteger() - } - - func decode(_: Int16.Type) throws -> Int16 { - try self.decodeFixedWidthInteger() - } - - func decode(_: Int32.Type) throws -> Int32 { - try self.decodeFixedWidthInteger() - } - - func decode(_: Int64.Type) throws -> Int64 { - try self.decodeFixedWidthInteger() - } - - func decode(_: UInt.Type) throws -> UInt { - try self.decodeFixedWidthInteger() - } - - func decode(_: UInt8.Type) throws -> UInt8 { - try self.decodeFixedWidthInteger() - } - - func decode(_: UInt16.Type) throws -> UInt16 { - try self.decodeFixedWidthInteger() - } - - func decode(_: UInt32.Type) throws -> UInt32 { - try self.decodeFixedWidthInteger() - } - - func decode(_: UInt64.Type) throws -> UInt64 { - try self.decodeFixedWidthInteger() - } - - func decode(_: T.Type) throws -> T where T: Decodable { - try T(from: self.impl) - } - - @inline(__always) private func createTypeMismatchError(type: Any.Type, value: AttributeValue) -> DecodingError { - DecodingError.typeMismatch(type, .init( - codingPath: self.codingPath, - debugDescription: "Expected to decode \(type) but found \(value.debugDataTypeDescription) instead." - )) - } - - @inline(__always) private func decodeFixedWidthInteger() throws - -> T { - guard case .number(let number) = self.value else { - throw self.createTypeMismatchError(type: T.self, value: self.value) - } - - guard let integer = T(number) else { - throw DecodingError.dataCorruptedError( - in: self, - debugDescription: "Parsed JSON number <\(number)> does not fit in \(T.self)." - ) - } - - return integer - } - - @inline(__always) private func decodeLosslessStringConvertible() - throws -> T { - guard case .number(let number) = self.value else { - throw self.createTypeMismatchError(type: T.self, value: self.value) - } - - guard let floatingPoint = T(number) else { - throw DecodingError.dataCorruptedError( - in: self, - debugDescription: "Parsed JSON number <\(number)> does not fit in \(T.self)." - ) - } - - return floatingPoint - } - } - - struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer { - let impl: _DecoderImpl - let codingPath: [CodingKey] - let array: [AttributeValue] - - let count: Int? // protocol requirement to be optional - var isAtEnd = false - var currentIndex = 0 - - init(impl: _DecoderImpl, codingPath: [CodingKey], array: [AttributeValue]) { - self.impl = impl - self.codingPath = codingPath - self.array = array - self.count = array.count - } - - mutating func decodeNil() throws -> Bool { - if self.array[self.currentIndex] == .null { - defer { - currentIndex += 1 - if currentIndex == count { - isAtEnd = true - } - } - return true - } - - // The protocol states: - // If the value is not null, does not increment currentIndex. - return false - } - - mutating func decode(_ type: Bool.Type) throws -> Bool { - defer { - currentIndex += 1 - if currentIndex == count { - isAtEnd = true - } - } - - guard case .boolean(let bool) = self.array[self.currentIndex] else { - throw self.createTypeMismatchError(type: type, value: self.array[self.currentIndex]) - } - - return bool - } - - mutating func decode(_ type: String.Type) throws -> String { - defer { - currentIndex += 1 - if currentIndex == count { - isAtEnd = true - } - } - - guard case .string(let string) = self.array[self.currentIndex] else { - throw self.createTypeMismatchError(type: type, value: self.array[self.currentIndex]) - } - - return string - } - - mutating func decode(_: Double.Type) throws -> Double { - try self.decodeLosslessStringConvertible() - } - - mutating func decode(_: Float.Type) throws -> Float { - try self.decodeLosslessStringConvertible() - } - - mutating func decode(_: Int.Type) throws -> Int { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: Int8.Type) throws -> Int8 { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: Int16.Type) throws -> Int16 { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: Int32.Type) throws -> Int32 { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: Int64.Type) throws -> Int64 { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: UInt.Type) throws -> UInt { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: UInt8.Type) throws -> UInt8 { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: UInt16.Type) throws -> UInt16 { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: UInt32.Type) throws -> UInt32 { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: UInt64.Type) throws -> UInt64 { - try self.decodeFixedWidthInteger() - } - - mutating func decode(_: T.Type) throws -> T where T: Decodable { - defer { - currentIndex += 1 - if currentIndex == count { - isAtEnd = true - } - } - - let json = self.array[self.currentIndex] - var newPath = self.codingPath - newPath.append(ArrayKey(index: self.currentIndex)) - let decoder = _DecoderImpl(userInfo: impl.userInfo, from: json, codingPath: newPath) - - return try T(from: decoder) - } - - mutating func nestedContainer(keyedBy type: NestedKey.Type) throws - -> KeyedDecodingContainer where NestedKey: CodingKey { - try self.impl.container(keyedBy: type) - } - - mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - try self.impl.unkeyedContainer() - } - - mutating func superDecoder() throws -> Swift.Decoder { - self.impl - } - - @inline(__always) private func createTypeMismatchError(type: Any.Type, value: AttributeValue) -> DecodingError { - let codingPath = self.codingPath + [ArrayKey(index: self.currentIndex)] - return DecodingError.typeMismatch(type, .init( - codingPath: codingPath, debugDescription: "Expected to decode \(type) but found \(value.debugDataTypeDescription) instead." - )) - } - - @inline(__always) private mutating func decodeFixedWidthInteger() throws - -> T { - defer { - currentIndex += 1 - if currentIndex == count { - isAtEnd = true - } - } - - guard case .number(let number) = self.array[self.currentIndex] else { - throw self.createTypeMismatchError(type: T.self, value: self.array[self.currentIndex]) - } - - guard let integer = T(number) else { - throw DecodingError.dataCorruptedError(in: self, - debugDescription: "Parsed JSON number <\(number)> does not fit in \(T.self).") - } - - return integer - } - - @inline(__always) private mutating func decodeLosslessStringConvertible() - throws -> T { - defer { - currentIndex += 1 - if currentIndex == count { - isAtEnd = true - } - } - - guard case .number(let number) = self.array[self.currentIndex] else { - throw self.createTypeMismatchError(type: T.self, value: self.array[self.currentIndex]) - } - - guard let float = T(number) else { - throw DecodingError.dataCorruptedError(in: self, - debugDescription: "Parsed JSON number <\(number)> does not fit in \(T.self).") - } - - return float - } - } -} - -extension DynamoDB.AttributeValue { - fileprivate var debugDataTypeDescription: String { - switch self { - case .list: - return "a list" - case .boolean: - return "boolean" - case .number: - return "a number" - case .string: - return "a string" - case .map: - return "a map" - case .null: - return "null" - case .binary: - return "bytes" - case .binarySet: - return "a set of bytes" - case .stringSet: - return "a set of strings" - case .numberSet: - return "a set of numbers" - } - } -} diff --git a/Sources/AWSLambdaEvents/S3.swift b/Sources/AWSLambdaEvents/S3.swift deleted file mode 100644 index d90de568..00000000 --- a/Sources/AWSLambdaEvents/S3.swift +++ /dev/null @@ -1,81 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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://docs.aws.amazon.com/lambda/latest/dg/with-s3.html - -public enum S3 { - public struct Event: Decodable { - public struct Record: Decodable { - public let eventVersion: String - public let eventSource: String - public let awsRegion: AWSRegion - - @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 - /// The object's size in bytes. - /// - /// Note: This property is available for all event types except "ObjectRemoved:*" - public let size: UInt64? - public let urlDecodedKey: String? - public let versionId: String? - public let eTag: String - public let sequencer: String - } -} diff --git a/Sources/AWSLambdaEvents/SES.swift b/Sources/AWSLambdaEvents/SES.swift deleted file mode 100644 index 4c5b3719..00000000 --- a/Sources/AWSLambdaEvents/SES.swift +++ /dev/null @@ -1,100 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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://docs.aws.amazon.com/lambda/latest/dg/services-ses.html - -public enum SES { - public struct Event: Decodable { - public struct Record: Decodable { - public let eventSource: String - public let eventVersion: String - public let ses: Message - } - - public let records: [Record] - - public enum CodingKeys: String, CodingKey { - case records = "Records" - } - } - - public struct Message: Decodable { - public let mail: Mail - public let receipt: Receipt - } - - public struct Mail: Decodable { - public let commonHeaders: CommonHeaders - public let destination: [String] - public let headers: [Header] - public let headersTruncated: Bool - public let messageId: String - public let source: String - @ISO8601WithFractionalSecondsCoding public var timestamp: Date - } - - public struct CommonHeaders: Decodable { - public let bcc: [String]? - public let cc: [String]? - @RFC5322DateTimeCoding public var date: Date - public let from: [String] - public let messageId: String - public let returnPath: String? - public let subject: String? - public let to: [String]? - } - - public struct Header: Decodable { - public let name: String - public let value: String - } - - public struct Receipt: Decodable { - public let action: Action - public let dmarcPolicy: DMARCPolicy? - public let dmarcVerdict: Verdict? - public let dkimVerdict: Verdict - public let processingTimeMillis: Int - public let recipients: [String] - public let spamVerdict: Verdict - public let spfVerdict: Verdict - @ISO8601WithFractionalSecondsCoding public var timestamp: Date - public let virusVerdict: Verdict - } - - public struct Action: Decodable { - public let functionArn: String - public let invocationType: String - public let type: String - } - - public struct Verdict: Decodable { - public let status: Status - } - - public enum DMARCPolicy: String, Decodable { - case none - case quarantine - case reject - } - - public enum Status: String, Decodable { - case pass = "PASS" - case fail = "FAIL" - case gray = "GRAY" - case processingFailed = "PROCESSING_FAILED" - } -} diff --git a/Sources/AWSLambdaEvents/SNS.swift b/Sources/AWSLambdaEvents/SNS.swift deleted file mode 100644 index a7ec21f6..00000000 --- a/Sources/AWSLambdaEvents/SNS.swift +++ /dev/null @@ -1,108 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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://docs.aws.amazon.com/lambda/latest/dg/with-sns.html - -public enum SNS { - public struct Event: Decodable { - public struct Record: Decodable { - public let eventVersion: String - public let eventSubscriptionArn: String - public let eventSource: String - public let sns: Message - - public enum CodingKeys: String, CodingKey { - case eventVersion = "EventVersion" - case eventSubscriptionArn = "EventSubscriptionArn" - case eventSource = "EventSource" - case sns = "Sns" - } - } - - public let records: [Record] - - public enum CodingKeys: String, CodingKey { - case records = "Records" - } - } - - public struct Message { - public enum Attribute { - case string(String) - case binary([UInt8]) - } - - public let signature: String - public let messageId: String - public let type: String - public let topicArn: String - public let messageAttributes: [String: Attribute]? - public let signatureVersion: String - - @ISO8601WithFractionalSecondsCoding - public var timestamp: Date - public let signingCertURL: String - public let message: String - public let unsubscribeUrl: String - public let subject: String? - } -} - -extension SNS.Message: Decodable { - enum CodingKeys: String, CodingKey { - case signature = "Signature" - case messageId = "MessageId" - case type = "Type" - case topicArn = "TopicArn" - case messageAttributes = "MessageAttributes" - case signatureVersion = "SignatureVersion" - case timestamp = "Timestamp" - case signingCertURL = "SigningCertUrl" - case message = "Message" - case unsubscribeUrl = "UnsubscribeUrl" - case subject = "Subject" - } -} - -extension SNS.Message.Attribute: Equatable {} - -extension SNS.Message.Attribute: Decodable { - enum CodingKeys: String, CodingKey { - case dataType = "Type" - case dataValue = "Value" - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let dataType = try container.decode(String.self, forKey: .dataType) - // https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html#SNSMessageAttributes.DataTypes - switch dataType { - case "String": - let value = try container.decode(String.self, forKey: .dataValue) - self = .string(value) - case "Binary": - let base64encoded = try container.decode(String.self, forKey: .dataValue) - let bytes = try base64encoded.base64decoded() - self = .binary(bytes) - default: - throw DecodingError.dataCorruptedError(forKey: .dataType, in: container, debugDescription: """ - Unexpected value \"\(dataType)\" for key \(CodingKeys.dataType). - Expected `String` or `Binary`. - """) - } - } -} diff --git a/Sources/AWSLambdaEvents/SQS.swift b/Sources/AWSLambdaEvents/SQS.swift deleted file mode 100644 index f2aca1fd..00000000 --- a/Sources/AWSLambdaEvents/SQS.swift +++ /dev/null @@ -1,97 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -// https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html - -public enum SQS { - public struct Event: Decodable { - public let records: [Message] - - enum CodingKeys: String, CodingKey { - case records = "Records" - } - } - - public struct Message { - /// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html - public enum Attribute { - case string(String) - case binary([UInt8]) - case number(String) - } - - public let messageId: String - public let receiptHandle: String - public var body: String - public let md5OfBody: String - public let md5OfMessageAttributes: String? - public let attributes: [String: String] - public let messageAttributes: [String: Attribute] - public let eventSourceArn: String - public let eventSource: String - public let awsRegion: AWSRegion - } -} - -extension SQS.Message: Decodable { - enum CodingKeys: String, CodingKey { - case messageId - case receiptHandle - case body - case md5OfBody - case md5OfMessageAttributes - case attributes - case messageAttributes - case eventSourceArn = "eventSourceARN" - case eventSource - case awsRegion - } -} - -extension SQS.Message.Attribute: Equatable {} - -extension SQS.Message.Attribute: Decodable { - enum CodingKeys: String, CodingKey { - case dataType - case stringValue - case binaryValue - - // BinaryListValue and StringListValue are unimplemented since - // they are not implemented as discussed here: - // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let dataType = try container.decode(String.self, forKey: .dataType) - switch dataType { - case "String": - let value = try container.decode(String.self, forKey: .stringValue) - self = .string(value) - case "Number": - let value = try container.decode(String.self, forKey: .stringValue) - self = .number(value) - case "Binary": - let base64encoded = try container.decode(String.self, forKey: .binaryValue) - let bytes = try base64encoded.base64decoded() - self = .binary(bytes) - default: - throw DecodingError.dataCorruptedError(forKey: .dataType, in: container, debugDescription: """ - Unexpected value \"\(dataType)\" for key \(CodingKeys.dataType). - Expected `String`, `Binary` or `Number`. - """) - } - } -} diff --git a/Sources/AWSLambdaEvents/Utils/Base64.swift b/Sources/AWSLambdaEvents/Utils/Base64.swift deleted file mode 100644 index 310a6aa5..00000000 --- a/Sources/AWSLambdaEvents/Utils/Base64.swift +++ /dev/null @@ -1,219 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -//===----------------------------------------------------------------------===// -// This is a vendored version from: -// https://github.com/fabianfett/swift-base64-kit - -struct Base64 {} - -// MARK: - Decode - - -extension Base64 { - struct DecodingOptions: OptionSet { - let rawValue: UInt - init(rawValue: UInt) { self.rawValue = rawValue } - - static let base64UrlAlphabet = DecodingOptions(rawValue: UInt(1 << 0)) - } - - enum DecodingError: Error, Equatable { - case invalidLength - case invalidCharacter(UInt8) - case unexpectedPaddingCharacter - case unexpectedEnd - } - - @inlinable - static func decode(encoded: Buffer, options: DecodingOptions = []) - throws -> [UInt8] where Buffer.Element == UInt8 { - let alphabet = options.contains(.base64UrlAlphabet) - ? Base64.decodeBase64Url - : Base64.decodeBase64 - - // In Base64 4 encoded bytes, become 3 decoded bytes. We pad to the - // nearest multiple of three. - let inputLength = encoded.count - guard inputLength > 0 else { return [] } - guard inputLength % 4 == 0 else { - throw DecodingError.invalidLength - } - - let inputBlocks = (inputLength + 3) / 4 - let fullQualified = inputBlocks - 1 - let outputLength = ((encoded.count + 3) / 4) * 3 - var iterator = encoded.makeIterator() - var outputBytes = [UInt8]() - outputBytes.reserveCapacity(outputLength) - - // fast loop. we don't expect any padding in here. - for _ in 0 ..< fullQualified { - let firstValue: UInt8 = try iterator.nextValue(alphabet: alphabet) - let secondValue: UInt8 = try iterator.nextValue(alphabet: alphabet) - let thirdValue: UInt8 = try iterator.nextValue(alphabet: alphabet) - let forthValue: UInt8 = try iterator.nextValue(alphabet: alphabet) - - outputBytes.append((firstValue << 2) | (secondValue >> 4)) - outputBytes.append((secondValue << 4) | (thirdValue >> 2)) - outputBytes.append((thirdValue << 6) | forthValue) - } - - // last 4 bytes. we expect padding characters in three and four - let firstValue: UInt8 = try iterator.nextValue(alphabet: alphabet) - let secondValue: UInt8 = try iterator.nextValue(alphabet: alphabet) - let thirdValue: UInt8? = try iterator.nextValueOrEmpty(alphabet: alphabet) - let forthValue: UInt8? = try iterator.nextValueOrEmpty(alphabet: alphabet) - - outputBytes.append((firstValue << 2) | (secondValue >> 4)) - if let thirdValue = thirdValue { - outputBytes.append((secondValue << 4) | (thirdValue >> 2)) - - if let forthValue = forthValue { - outputBytes.append((thirdValue << 6) | forthValue) - } - } - - return outputBytes - } - - @inlinable - static func decode(encoded: String, options: DecodingOptions = []) throws -> [UInt8] { - // A string can be backed by a contiguous storage (pure swift string) - // or a nsstring (bridged string from objc). We only get a pointer - // to the contiguous storage, if the input string is a swift string. - // Therefore to transform the nsstring backed input into a swift - // string we concat the input with nothing, causing a copy on write - // into a swift string. - let decoded = try encoded.utf8.withContiguousStorageIfAvailable { pointer in - try self.decode(encoded: pointer, options: options) - } - - if decoded != nil { - return decoded! - } - - return try self.decode(encoded: encoded + "", options: options) - } - - // MARK: Internal - - @usableFromInline - static let decodeBase64: [UInt8] = [ - // 0 1 2 3 4 5 6 7 8 9 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 1 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 2 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 3 - 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, // 4 - 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, // 5 - 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, // 6 - 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 7 - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 8 - 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, // 9 - 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // 10 - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // 11 - 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, // 12 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 13 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 14 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 15 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 16 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 17 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 18 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 19 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 20 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 21 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 22 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 23 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 24 - 255, 255, 255, 255, 255, // 25 - ] - - @usableFromInline - static let decodeBase64Url: [UInt8] = [ - // 0 1 2 3 4 5 6 7 8 9 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 1 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 2 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 3 - 255, 255, 255, 255, 255, 62, 255, 255, 52, 53, // 4 - 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, // 5 - 255, 254, 255, 255, 255, 0, 1, 2, 3, 4, // 6 - 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 7 - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 8 - 25, 255, 255, 255, 255, 63, 255, 26, 27, 28, // 9 - 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // 10 - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // 11 - 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, // 12 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 13 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 14 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 15 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 16 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 17 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 18 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 19 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 20 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 21 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 22 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 23 - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 24 - 255, 255, 255, 255, 255, // 25 - ] - - @usableFromInline - static let paddingCharacter: UInt8 = 254 -} - -extension IteratorProtocol where Self.Element == UInt8 { - mutating func nextValue(alphabet: [UInt8]) throws -> UInt8 { - let ascii = self.next()! - - let value = alphabet[Int(ascii)] - - if value < 64 { - return value - } - - if value == Base64.paddingCharacter { - throw Base64.DecodingError.unexpectedPaddingCharacter - } - - throw Base64.DecodingError.invalidCharacter(ascii) - } - - mutating func nextValueOrEmpty(alphabet: [UInt8]) throws -> UInt8? { - let ascii = self.next()! - - let value = alphabet[Int(ascii)] - - if value < 64 { - return value - } - - if value == Base64.paddingCharacter { - return nil - } - - throw Base64.DecodingError.invalidCharacter(ascii) - } -} - -// MARK: - Extensions - - -extension String { - func base64decoded(options: Base64.DecodingOptions = []) throws -> [UInt8] { - // In Base64, 3 bytes become 4 output characters, and we pad to the nearest multiple - // of four. - try Base64.decode(encoded: self, options: options) - } -} diff --git a/Sources/AWSLambdaEvents/Utils/DateWrappers.swift b/Sources/AWSLambdaEvents/Utils/DateWrappers.swift deleted file mode 100644 index 3b7fc481..00000000 --- a/Sources/AWSLambdaEvents/Utils/DateWrappers.swift +++ /dev/null @@ -1,108 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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.DateFormatter -import struct Foundation.Locale -import struct Foundation.TimeZone - -@propertyWrapper -public struct ISO8601Coding: 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, but `\(dateString)` is not in the correct format") - } - self.wrappedValue = date - } - - private static let dateFormatter: DateFormatter = Self.createDateFormatter() - - private static func createDateFormatter() -> DateFormatter { - let formatter = DateFormatter() - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" - return formatter - } -} - -@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)` is not in the correct format") - } - self.wrappedValue = date - } - - private static let dateFormatter: DateFormatter = Self.createDateFormatter() - - private static func createDateFormatter() -> DateFormatter { - let formatter = DateFormatter() - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" - return formatter - } -} - -@propertyWrapper -public struct RFC5322DateTimeCoding: Decodable { - public let wrappedValue: Date - - public init(wrappedValue: Date) { - self.wrappedValue = wrappedValue - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - var string = try container.decode(String.self) - // RFC5322 dates sometimes have the alphabetic version of the timezone in brackets after the numeric version. The date formatter - // fails to parse this so we need to remove this before parsing. - if let bracket = string.firstIndex(of: "(") { - string = String(string[string.startIndex ..< bracket].trimmingCharacters(in: .whitespaces)) - } - guard let date = Self.dateFormatter.date(from: string) else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: - "Expected date to be in RFC5322 date-time format with fractional seconds, but `\(string)` is not in the correct format") - } - self.wrappedValue = date - } - - private static let dateFormatter: DateFormatter = Self.createDateFormatter() - private static func createDateFormatter() -> DateFormatter { - let formatter = DateFormatter() - formatter.dateFormat = "EEE, d MMM yyy HH:mm:ss z" - formatter.locale = Locale(identifier: "en_US_POSIX") - return formatter - } -} diff --git a/Sources/AWSLambdaEvents/Utils/HTTP.swift b/Sources/AWSLambdaEvents/Utils/HTTP.swift deleted file mode 100644 index 9e0d8f2d..00000000 --- a/Sources/AWSLambdaEvents/Utils/HTTP.swift +++ /dev/null @@ -1,187 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -// MARK: HTTPMethod - -public typealias HTTPHeaders = [String: String] -public typealias HTTPMultiValueHeaders = [String: [String]] - -public struct HTTPMethod: RawRepresentable, Equatable { - public var rawValue: String - - public init?(rawValue: String) { - guard rawValue.isValidHTTPToken else { - return nil - } - self.rawValue = rawValue - } - - public static var GET: HTTPMethod { HTTPMethod(rawValue: "GET")! } - public static var POST: HTTPMethod { HTTPMethod(rawValue: "POST")! } - public static var PUT: HTTPMethod { HTTPMethod(rawValue: "PUT")! } - public static var PATCH: HTTPMethod { HTTPMethod(rawValue: "PATCH")! } - public static var DELETE: HTTPMethod { HTTPMethod(rawValue: "DELETE")! } - public static var OPTIONS: HTTPMethod { HTTPMethod(rawValue: "OPTIONS")! } - public static var HEAD: HTTPMethod { HTTPMethod(rawValue: "HEAD")! } - - public static func RAW(value: String) -> HTTPMethod? { HTTPMethod(rawValue: value) } -} - -extension HTTPMethod: Codable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let rawMethod = try container.decode(String.self) - - guard let method = HTTPMethod(rawValue: rawMethod) else { - throw DecodingError.dataCorruptedError( - in: container, - debugDescription: #"Method "\#(rawMethod)" does not conform to allowed http method syntax defined in RFC 7230 Section 3.2.6"# - ) - } - - self = method - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self.rawValue) - } -} - -// MARK: HTTPResponseStatus - -public struct HTTPResponseStatus { - public let code: UInt - public let reasonPhrase: String? - - public init(code: UInt, reasonPhrase: String? = nil) { - self.code = code - self.reasonPhrase = reasonPhrase - } - - public static var `continue`: HTTPResponseStatus { HTTPResponseStatus(code: 100) } - public static var switchingProtocols: HTTPResponseStatus { HTTPResponseStatus(code: 101) } - public static var processing: HTTPResponseStatus { HTTPResponseStatus(code: 102) } - public static var earlyHints: HTTPResponseStatus { HTTPResponseStatus(code: 103) } - - public static var ok: HTTPResponseStatus { HTTPResponseStatus(code: 200) } - public static var created: HTTPResponseStatus { HTTPResponseStatus(code: 201) } - public static var accepted: HTTPResponseStatus { HTTPResponseStatus(code: 202) } - public static var nonAuthoritativeInformation: HTTPResponseStatus { HTTPResponseStatus(code: 203) } - public static var noContent: HTTPResponseStatus { HTTPResponseStatus(code: 204) } - public static var resetContent: HTTPResponseStatus { HTTPResponseStatus(code: 205) } - public static var partialContent: HTTPResponseStatus { HTTPResponseStatus(code: 206) } - public static var multiStatus: HTTPResponseStatus { HTTPResponseStatus(code: 207) } - public static var alreadyReported: HTTPResponseStatus { HTTPResponseStatus(code: 208) } - public static var imUsed: HTTPResponseStatus { HTTPResponseStatus(code: 226) } - - public static var multipleChoices: HTTPResponseStatus { HTTPResponseStatus(code: 300) } - public static var movedPermanently: HTTPResponseStatus { HTTPResponseStatus(code: 301) } - public static var found: HTTPResponseStatus { HTTPResponseStatus(code: 302) } - public static var seeOther: HTTPResponseStatus { HTTPResponseStatus(code: 303) } - public static var notModified: HTTPResponseStatus { HTTPResponseStatus(code: 304) } - public static var useProxy: HTTPResponseStatus { HTTPResponseStatus(code: 305) } - public static var temporaryRedirect: HTTPResponseStatus { HTTPResponseStatus(code: 307) } - public static var permanentRedirect: HTTPResponseStatus { HTTPResponseStatus(code: 308) } - - public static var badRequest: HTTPResponseStatus { HTTPResponseStatus(code: 400) } - public static var unauthorized: HTTPResponseStatus { HTTPResponseStatus(code: 401) } - public static var paymentRequired: HTTPResponseStatus { HTTPResponseStatus(code: 402) } - public static var forbidden: HTTPResponseStatus { HTTPResponseStatus(code: 403) } - public static var notFound: HTTPResponseStatus { HTTPResponseStatus(code: 404) } - public static var methodNotAllowed: HTTPResponseStatus { HTTPResponseStatus(code: 405) } - public static var notAcceptable: HTTPResponseStatus { HTTPResponseStatus(code: 406) } - public static var proxyAuthenticationRequired: HTTPResponseStatus { HTTPResponseStatus(code: 407) } - public static var requestTimeout: HTTPResponseStatus { HTTPResponseStatus(code: 408) } - public static var conflict: HTTPResponseStatus { HTTPResponseStatus(code: 409) } - public static var gone: HTTPResponseStatus { HTTPResponseStatus(code: 410) } - public static var lengthRequired: HTTPResponseStatus { HTTPResponseStatus(code: 411) } - public static var preconditionFailed: HTTPResponseStatus { HTTPResponseStatus(code: 412) } - public static var payloadTooLarge: HTTPResponseStatus { HTTPResponseStatus(code: 413) } - public static var uriTooLong: HTTPResponseStatus { HTTPResponseStatus(code: 414) } - public static var unsupportedMediaType: HTTPResponseStatus { HTTPResponseStatus(code: 415) } - public static var rangeNotSatisfiable: HTTPResponseStatus { HTTPResponseStatus(code: 416) } - public static var expectationFailed: HTTPResponseStatus { HTTPResponseStatus(code: 417) } - public static var imATeapot: HTTPResponseStatus { HTTPResponseStatus(code: 418) } - public static var misdirectedRequest: HTTPResponseStatus { HTTPResponseStatus(code: 421) } - public static var unprocessableEntity: HTTPResponseStatus { HTTPResponseStatus(code: 422) } - public static var locked: HTTPResponseStatus { HTTPResponseStatus(code: 423) } - public static var failedDependency: HTTPResponseStatus { HTTPResponseStatus(code: 424) } - public static var upgradeRequired: HTTPResponseStatus { HTTPResponseStatus(code: 426) } - public static var preconditionRequired: HTTPResponseStatus { HTTPResponseStatus(code: 428) } - public static var tooManyRequests: HTTPResponseStatus { HTTPResponseStatus(code: 429) } - public static var requestHeaderFieldsTooLarge: HTTPResponseStatus { HTTPResponseStatus(code: 431) } - public static var unavailableForLegalReasons: HTTPResponseStatus { HTTPResponseStatus(code: 451) } - - public static var internalServerError: HTTPResponseStatus { HTTPResponseStatus(code: 500) } - public static var notImplemented: HTTPResponseStatus { HTTPResponseStatus(code: 501) } - public static var badGateway: HTTPResponseStatus { HTTPResponseStatus(code: 502) } - public static var serviceUnavailable: HTTPResponseStatus { HTTPResponseStatus(code: 503) } - public static var gatewayTimeout: HTTPResponseStatus { HTTPResponseStatus(code: 504) } - public static var httpVersionNotSupported: HTTPResponseStatus { HTTPResponseStatus(code: 505) } - public static var variantAlsoNegotiates: HTTPResponseStatus { HTTPResponseStatus(code: 506) } - public static var insufficientStorage: HTTPResponseStatus { HTTPResponseStatus(code: 507) } - public static var loopDetected: HTTPResponseStatus { HTTPResponseStatus(code: 508) } - public static var notExtended: HTTPResponseStatus { HTTPResponseStatus(code: 510) } - public static var networkAuthenticationRequired: HTTPResponseStatus { HTTPResponseStatus(code: 511) } -} - -extension HTTPResponseStatus: Equatable { - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.code == rhs.code - } -} - -extension HTTPResponseStatus: Codable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - self.code = try container.decode(UInt.self) - self.reasonPhrase = nil - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self.code) - } -} - -extension String { - internal var isValidHTTPToken: Bool { - self.utf8.allSatisfy { (char) -> Bool in - switch char { - case UInt8(ascii: "a") ... UInt8(ascii: "z"), - UInt8(ascii: "A") ... UInt8(ascii: "Z"), - UInt8(ascii: "0") ... UInt8(ascii: "9"), - UInt8(ascii: "!"), - UInt8(ascii: "#"), - UInt8(ascii: "$"), - UInt8(ascii: "%"), - UInt8(ascii: "&"), - UInt8(ascii: "'"), - UInt8(ascii: "*"), - UInt8(ascii: "+"), - UInt8(ascii: "-"), - UInt8(ascii: "."), - UInt8(ascii: "^"), - UInt8(ascii: "_"), - UInt8(ascii: "`"), - UInt8(ascii: "|"), - UInt8(ascii: "~"): - return true - default: - return false - } - } - } -} diff --git a/Tests/AWSLambdaEventsTests/ALBTests.swift b/Tests/AWSLambdaEventsTests/ALBTests.swift deleted file mode 100644 index b24684c9..00000000 --- a/Tests/AWSLambdaEventsTests/ALBTests.swift +++ /dev/null @@ -1,64 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 ALBTests: XCTestCase { - static let exampleSingleValueHeadersEventBody = """ - { - "requestContext":{ - "elb":{ - "targetGroupArn": "arn:aws:elasticloadbalancing:eu-central-1:079477498937:targetgroup/EinSternDerDeinenNamenTraegt/621febf5a44b2ce5" - } - }, - "httpMethod": "GET", - "path": "/", - "queryStringParameters": {}, - "headers":{ - "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - "accept-encoding": "gzip, deflate", - "accept-language": "en-us", - "connection": "keep-alive", - "host": "event-testl-1wa3wrvmroilb-358275751.eu-central-1.elb.amazonaws.com", - "upgrade-insecure-requests": "1", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.2 Safari/605.1.15", - "x-amzn-trace-id": "Root=1-5e189143-ad18a2b0a7728cd0dac45e10", - "x-forwarded-for": "90.187.8.137", - "x-forwarded-port": "80", - "x-forwarded-proto": "http" - }, - "body":"", - "isBase64Encoded":false - } - """ - - func testRequestWithSingleValueHeadersEvent() { - let data = ALBTests.exampleSingleValueHeadersEventBody.data(using: .utf8)! - do { - let decoder = JSONDecoder() - - let event = try decoder.decode(ALB.TargetGroupRequest.self, from: data) - - XCTAssertEqual(event.httpMethod, .GET) - XCTAssertEqual(event.body, "") - XCTAssertEqual(event.isBase64Encoded, false) - XCTAssertEqual(event.headers?.count, 11) - XCTAssertEqual(event.path, "/") - XCTAssertEqual(event.queryStringParameters, [:]) - } catch { - XCTFail("Unexpected error: \(error)") - } - } -} diff --git a/Tests/AWSLambdaEventsTests/APIGateway+V2Tests.swift b/Tests/AWSLambdaEventsTests/APIGateway+V2Tests.swift deleted file mode 100644 index 9d682c94..00000000 --- a/Tests/AWSLambdaEventsTests/APIGateway+V2Tests.swift +++ /dev/null @@ -1,91 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 APIGatewayV2Tests: XCTestCase { - static let exampleGetEventBody = """ - { - "routeKey":"GET /hello", - "version":"2.0", - "rawPath":"/hello", - "stageVariables":{ - "foo":"bar" - }, - "requestContext":{ - "timeEpoch":1587750461466, - "domainPrefix":"hello", - "authorizer":{ - "jwt":{ - "scopes":[ - "hello" - ], - "claims":{ - "aud":"customers", - "iss":"https://hello.test.com/", - "iat":"1587749276", - "exp":"1587756476" - } - } - }, - "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":"GET", - "protocol":"HTTP/1.1", - "sourceIp":"91.64.117.86" - }, - "time":"24/Apr/2020:17:47:41 +0000" - }, - "isBase64Encoded":false, - "rawQueryString":"foo=bar", - "queryStringParameters":{ - "foo":"bar" - }, - "headers":{ - "x-forwarded-proto":"https", - "x-forwarded-for":"91.64.117.86", - "x-forwarded-port":"443", - "authorization":"Bearer abc123", - "host":"hello.test.com", - "x-amzn-trace-id":"Root=1-5ea3263d-07c5d5ddfd0788bed7dad831", - "user-agent":"Paw/3.1.10 (Macintosh; OS X/10.15.4) GCDHTTPRequest", - "content-length":"0" - } - } - """ - - // MARK: - Request - - - // MARK: Decoding - - func testRequestDecodingExampleGetRequest() { - let data = APIGatewayV2Tests.exampleGetEventBody.data(using: .utf8)! - var req: APIGateway.V2.Request? - XCTAssertNoThrow(req = try JSONDecoder().decode(APIGateway.V2.Request.self, from: data)) - - XCTAssertEqual(req?.rawPath, "/hello") - XCTAssertEqual(req?.context.http.method, .GET) - XCTAssertEqual(req?.queryStringParameters?.count, 1) - XCTAssertEqual(req?.rawQueryString, "foo=bar") - XCTAssertEqual(req?.headers.count, 8) - XCTAssertNil(req?.body) - } -} diff --git a/Tests/AWSLambdaEventsTests/APIGatewayTests.swift b/Tests/AWSLambdaEventsTests/APIGatewayTests.swift deleted file mode 100644 index 37cba98a..00000000 --- a/Tests/AWSLambdaEventsTests/APIGatewayTests.swift +++ /dev/null @@ -1,77 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 APIGatewayTests: XCTestCase { - static let exampleGetEventBody = """ - {"httpMethod": "GET", "body": null, "resource": "/test", "requestContext": {"resourceId": "123456", "apiId": "1234567890", "resourcePath": "/test", "httpMethod": "GET", "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", "accountId": "123456789012", "stage": "Prod", "identity": {"apiKey": null, "userArn": null, "cognitoAuthenticationType": null, "caller": null, "userAgent": "Custom User Agent String", "user": null, "cognitoIdentityPoolId": null, "cognitoAuthenticationProvider": null, "sourceIp": "127.0.0.1", "accountId": null}, "extendedRequestId": null, "path": "/test"}, "queryStringParameters": null, "multiValueQueryStringParameters": null, "headers": {"Host": "127.0.0.1:3000", "Connection": "keep-alive", "Cache-Control": "max-age=0", "Dnt": "1", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 Edg/78.0.276.24", "Sec-Fetch-User": "?1", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", "Sec-Fetch-Site": "none", "Sec-Fetch-Mode": "navigate", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "X-Forwarded-Proto": "http", "X-Forwarded-Port": "3000"}, "multiValueHeaders": {"Host": ["127.0.0.1:3000"], "Connection": ["keep-alive"], "Cache-Control": ["max-age=0"], "Dnt": ["1"], "Upgrade-Insecure-Requests": ["1"], "User-Agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 Edg/78.0.276.24"], "Sec-Fetch-User": ["?1"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"], "Sec-Fetch-Site": ["none"], "Sec-Fetch-Mode": ["navigate"], "Accept-Encoding": ["gzip, deflate, br"], "Accept-Language": ["en-US,en;q=0.9"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Port": ["3000"]}, "pathParameters": null, "stageVariables": null, "path": "/test", "isBase64Encoded": false} - """ - - static let todoPostEventBody = """ - {"httpMethod": "POST", "body": "{\\"title\\":\\"a todo\\"}", "resource": "/todos", "requestContext": {"resourceId": "123456", "apiId": "1234567890", "resourcePath": "/todos", "httpMethod": "POST", "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", "accountId": "123456789012", "stage": "test", "identity": {"apiKey": null, "userArn": null, "cognitoAuthenticationType": null, "caller": null, "userAgent": "Custom User Agent String", "user": null, "cognitoIdentityPoolId": null, "cognitoAuthenticationProvider": null, "sourceIp": "127.0.0.1", "accountId": null}, "extendedRequestId": null, "path": "/todos"}, "queryStringParameters": null, "multiValueQueryStringParameters": null, "headers": {"Host": "127.0.0.1:3000", "Connection": "keep-alive", "Content-Length": "18", "Pragma": "no-cache", "Cache-Control": "no-cache", "Accept": "text/plain, */*; q=0.01", "Origin": "http://todobackend.com", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.36 Safari/537.36 Edg/79.0.309.25", "Dnt": "1", "Content-Type": "application/json", "Sec-Fetch-Site": "cross-site", "Sec-Fetch-Mode": "cors", "Referer": "http://todobackend.com/specs/index.html?http://127.0.0.1:3000/todos", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "X-Forwarded-Proto": "http", "X-Forwarded-Port": "3000"}, "multiValueHeaders": {"Host": ["127.0.0.1:3000"], "Connection": ["keep-alive"], "Content-Length": ["18"], "Pragma": ["no-cache"], "Cache-Control": ["no-cache"], "Accept": ["text/plain, */*; q=0.01"], "Origin": ["http://todobackend.com"], "User-Agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.36 Safari/537.36 Edg/79.0.309.25"], "Dnt": ["1"], "Content-Type": ["application/json"], "Sec-Fetch-Site": ["cross-site"], "Sec-Fetch-Mode": ["cors"], "Referer": ["http://todobackend.com/specs/index.html?http://127.0.0.1:3000/todos"], "Accept-Encoding": ["gzip, deflate, br"], "Accept-Language": ["en-US,en;q=0.9"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Port": ["3000"]}, "pathParameters": null, "stageVariables": null, "path": "/todos", "isBase64Encoded": false} - """ - - // MARK: - Request - - - // MARK: Decoding - - func testRequestDecodingExampleGetRequest() { - let data = APIGatewayTests.exampleGetEventBody.data(using: .utf8)! - var req: APIGateway.Request? - XCTAssertNoThrow(req = try JSONDecoder().decode(APIGateway.Request.self, from: data)) - - XCTAssertEqual(req?.path, "/test") - XCTAssertEqual(req?.httpMethod, .GET) - } - - func testRequestDecodingTodoPostRequest() { - let data = APIGatewayTests.todoPostEventBody.data(using: .utf8)! - var req: APIGateway.Request? - XCTAssertNoThrow(req = try JSONDecoder().decode(APIGateway.Request.self, from: data)) - - XCTAssertEqual(req?.path, "/todos") - XCTAssertEqual(req?.httpMethod, .POST) - } - - // MARK: - Response - - - // MARK: Encoding - - struct JSONResponse: Codable { - let statusCode: UInt - let headers: [String: String]? - let body: String? - let isBase64Encoded: Bool? - } - - func testResponseEncoding() { - let resp = APIGateway.Response( - statusCode: .ok, - headers: ["Server": "Test"], - body: "abc123" - ) - - var data: Data? - XCTAssertNoThrow(data = try JSONEncoder().encode(resp)) - var json: JSONResponse? - XCTAssertNoThrow(json = try JSONDecoder().decode(JSONResponse.self, from: XCTUnwrap(data))) - - XCTAssertEqual(json?.statusCode, resp.statusCode.code) - XCTAssertEqual(json?.body, resp.body) - XCTAssertEqual(json?.isBase64Encoded, resp.isBase64Encoded) - XCTAssertEqual(json?.headers?["Server"], "Test") - } -} diff --git a/Tests/AWSLambdaEventsTests/AppSyncTests.swift b/Tests/AWSLambdaEventsTests/AppSyncTests.swift deleted file mode 100644 index 5aeda17d..00000000 --- a/Tests/AWSLambdaEventsTests/AppSyncTests.swift +++ /dev/null @@ -1,271 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 AppSyncTests: XCTestCase { - static let exampleEventBody = """ - { - "arguments": { - "id": "my identifier" - }, - "identity": { - "claims": { - "sub": "192879fc-a240-4bf1-ab5a-d6a00f3063f9", - "email_verified": true, - "iss": "https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx", - "phone_number_verified": false, - "cognito:username": "jdoe", - "aud": "7471s60os7h0uu77i1tk27sp9n", - "event_id": "bc334ed8-a938-4474-b644-9547e304e606", - "token_use": "id", - "auth_time": 1599154213, - "phone_number": "+19999999999", - "exp": 1599157813, - "iat": 1599154213, - "email": "jdoe@email.com" - }, - "defaultAuthStrategy": "ALLOW", - "groups": null, - "issuer": "https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx", - "sourceIp": [ - "1.1.1.1" - ], - "sub": "192879fc-a240-4bf1-ab5a-d6a00f3063f9", - "username": "jdoe" - }, - "source": null, - "request": { - "headers": { - "x-forwarded-for": "1.1.1.1, 2.2.2.2", - "cloudfront-viewer-country": "US", - "cloudfront-is-tablet-viewer": "false", - "via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)", - "cloudfront-forwarded-proto": "https", - "origin": "https://us-west-1.console.aws.amazon.com", - "content-length": "217", - "accept-language": "en-US,en;q=0.9", - "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com", - "x-forwarded-proto": "https", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", - "accept": "*/*", - "cloudfront-is-mobile-viewer": "false", - "cloudfront-is-smarttv-viewer": "false", - "accept-encoding": "gzip, deflate, br", - "referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1", - "content-type": "application/json", - "sec-fetch-mode": "cors", - "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==", - "x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714", - "authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...", - "sec-fetch-dest": "empty", - "x-amz-user-agent": "AWS-Console-AppSync/", - "cloudfront-is-desktop-viewer": "true", - "sec-fetch-site": "cross-site", - "x-forwarded-port": "443" - } - }, - "prev": null, - "info": { - "selectionSetList": [ - "id", - "field1", - "field2" - ], - "selectionSetGraphQL": "{ id }", - "parentTypeName": "Mutation", - "fieldName": "createSomething", - "variables": {} - }, - "stash": {} - } - """ - - // MARK: Decoding - - func testRequestDecodingExampleEvent() { - let data = AppSyncTests.exampleEventBody.data(using: .utf8)! - var event: AppSync.Event? - XCTAssertNoThrow(event = try JSONDecoder().decode(AppSync.Event.self, from: data)) - - XCTAssertNotNil(event?.arguments) - XCTAssertEqual(event?.arguments["id"], .string("my identifier")) - XCTAssertEqual(event?.info.fieldName, "createSomething") - XCTAssertEqual(event?.info.parentTypeName, "Mutation") - XCTAssertEqual(event?.info.selectionSetList, ["id", "field1", "field2"]) - XCTAssertEqual(event?.request.headers["accept-language"], "en-US,en;q=0.9") - - switch event?.identity { - case .cognitoUserPools(let cognitoIdentity): - XCTAssertEqual(cognitoIdentity.defaultAuthStrategy, "ALLOW") - XCTAssertEqual(cognitoIdentity.issuer, "https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx") - XCTAssertEqual(cognitoIdentity.sourceIp, ["1.1.1.1"]) - XCTAssertEqual(cognitoIdentity.username, "jdoe") - XCTAssertEqual(cognitoIdentity.sub, "192879fc-a240-4bf1-ab5a-d6a00f3063f9") - default: - XCTAssertTrue(false, "a cognito identity was expected, but didn't find one.") - } - } - - func testRequestDecodingEventWithSource() { - let eventBody = """ - { - "arguments": {}, - "identity": null, - "source": { - "name": "Hello", - "id": "1" - }, - "request": { - "headers": { - "x-forwarded-for": "1.1.1.1, 2.2.2.2", - "accept-encoding": "gzip, deflate, br", - "cloudfront-viewer-country": "CA", - "cloudfront-is-tablet-viewer": "false", - "referer": "https://us-west-2.console.aws.amazon.com/", - "via": "2.0 xxxxxx.cloudfront.net (CloudFront)", - "cloudfront-forwarded-proto": "https", - "origin": "https://us-west-2.console.aws.amazon.com", - "x-api-key": "xxxxxxxxxxxxxxxxxxxxx", - "content-type": "application/json", - "x-amzn-trace-id": "Root=1-5fcd9a24-364c62405b418bd53c7984ce", - "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==", - "content-length": "173", - "x-amz-user-agent": "AWS-Console-AppSync/", - "x-forwarded-proto": "https", - "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-2.amazonaws.com", - "accept-language": "en-ca", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15", - "cloudfront-is-desktop-viewer": "true", - "cloudfront-is-mobile-viewer": "false", - "accept": "*/*", - "x-forwarded-port": "443", - "cloudfront-is-smarttv-viewer": "false" - } - }, - "prev": null, - "info": { - "selectionSetList": [ - "address", - "id" - ], - "selectionSetGraphQL": "{ address id}", - "parentTypeName": "Customer", - "fieldName": "address", - "variables": {} - }, - "stash": {} - } - """ - - let data = eventBody.data(using: .utf8)! - var event: AppSync.Event? - XCTAssertNoThrow(event = try JSONDecoder().decode(AppSync.Event.self, from: data)) - XCTAssertEqual(event?.source?["name"], "Hello") - XCTAssertTrue(event?.stash?.isEmpty ?? false, "stash dictionary must be empty") - XCTAssertNil(event?.identity) - } - - func testRequestDecodingIamIdentity() { - let eventBody = """ - { - "arguments": {}, - "identity": { - "accountId" : "accountId1", - "cognitoIdentityPoolId" : "cognitoIdentityPool2", - "cognitoIdentityId" : "cognitoIdentity3", - "sourceIp" : ["1.1.1.1"], - "username" : null, - "userArn" : "arn123", - "cognitoIdentityAuthType" : "authenticated", - "cognitoIdentityAuthProvider" : "authprovider" - }, - "source": { - "name": "Hello", - "id": "1" - }, - "request": { - "headers": { - "x-forwarded-for": "1.1.1.1, 2.2.2.2", - "accept-encoding": "gzip, deflate, br", - "cloudfront-viewer-country": "CA", - "cloudfront-is-tablet-viewer": "false", - "referer": "https://us-west-2.console.aws.amazon.com/", - "via": "2.0 xxxxxx.cloudfront.net (CloudFront)", - "cloudfront-forwarded-proto": "https", - "origin": "https://us-west-2.console.aws.amazon.com", - "x-api-key": "xxxxxxxxxxxxxxxxxxxxx", - "content-type": "application/json", - "x-amzn-trace-id": "Root=1-5fcd9a24-364c62405b418bd53c7984ce", - "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==", - "content-length": "173", - "x-amz-user-agent": "AWS-Console-AppSync/", - "x-forwarded-proto": "https", - "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-2.amazonaws.com", - "accept-language": "en-ca", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15", - "cloudfront-is-desktop-viewer": "true", - "cloudfront-is-mobile-viewer": "false", - "accept": "*/*", - "x-forwarded-port": "443", - "cloudfront-is-smarttv-viewer": "false" - } - }, - "prev": null, - "info": { - "selectionSetList": [ - "address", - "id" - ], - "selectionSetGraphQL": "{ address id}", - "parentTypeName": "Customer", - "fieldName": "address", - "variables": {} - }, - "stash": {} - } - """ - - let data = eventBody.data(using: .utf8)! - var event: AppSync.Event? - XCTAssertNoThrow(event = try JSONDecoder().decode(AppSync.Event.self, from: data)) - switch event?.identity { - case .iam(let iamIdentity): - XCTAssertEqual(iamIdentity.accountId, "accountId1") - XCTAssertEqual(iamIdentity.cognitoIdentityPoolId, "cognitoIdentityPool2") - XCTAssertEqual(iamIdentity.cognitoIdentityId, "cognitoIdentity3") - XCTAssertEqual(iamIdentity.sourceIp, ["1.1.1.1"]) - XCTAssertNil(iamIdentity.username) - XCTAssertEqual(iamIdentity.userArn, "arn123") - XCTAssertEqual(iamIdentity.cognitoIdentityAuthType, "authenticated") - XCTAssertEqual(iamIdentity.cognitoIdentityAuthProvider, "authprovider") - default: - XCTAssertTrue(false, "an iam identity was expected, but didn't find one.") - } - } -} - -extension AppSync.Event.ArgumentValue: Equatable { - public static func == (lhs: Self, rhs: Self) -> Bool { - switch (lhs, rhs) { - case (.string(let lhsString), .string(let rhsString)): - return lhsString == rhsString - case (.dictionary(let lhsDictionary), .dictionary(let rhsDictionary)): - return lhsDictionary == rhsDictionary - default: - return false - } - } -} diff --git a/Tests/AWSLambdaEventsTests/CloudwatchTests.swift b/Tests/AWSLambdaEventsTests/CloudwatchTests.swift deleted file mode 100644 index b931e020..00000000 --- a/Tests/AWSLambdaEventsTests/CloudwatchTests.swift +++ /dev/null @@ -1,137 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 CloudwatchTests: XCTestCase { - static func eventBody(type: String, details: String) -> String { - """ - { - "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", - "detail-type": "\(type)", - "source": "aws.events", - "account": "123456789012", - "time": "1970-01-01T00:00:00Z", - "region": "us-east-1", - "resources": [ - "arn:aws:events:us-east-1:123456789012:rule/ExampleRule" - ], - "detail": \(details) - } - """ - } - - func testScheduledEventFromJSON() { - let eventBody = CloudwatchTests.eventBody(type: Cloudwatch.Scheduled.name, details: "{}") - let data = eventBody.data(using: .utf8)! - var maybeEvent: Cloudwatch.ScheduledEvent? - XCTAssertNoThrow(maybeEvent = try JSONDecoder().decode(Cloudwatch.ScheduledEvent.self, from: data)) - - guard let event = maybeEvent else { - return XCTFail("Expected to have an event") - } - - XCTAssertEqual(event.id, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c") - XCTAssertEqual(event.source, "aws.events") - XCTAssertEqual(event.accountId, "123456789012") - XCTAssertEqual(event.time, Date(timeIntervalSince1970: 0)) - XCTAssertEqual(event.region, .us_east_1) - XCTAssertEqual(event.resources, ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"]) - } - - func testEC2InstanceStateChangeNotificationEventFromJSON() { - let eventBody = CloudwatchTests.eventBody(type: Cloudwatch.EC2.InstanceStateChangeNotification.name, - details: "{ \"instance-id\": \"0\", \"state\": \"stopping\" }") - let data = eventBody.data(using: .utf8)! - var maybeEvent: Cloudwatch.EC2.InstanceStateChangeNotificationEvent? - XCTAssertNoThrow(maybeEvent = try JSONDecoder().decode(Cloudwatch.EC2.InstanceStateChangeNotificationEvent.self, from: data)) - - guard let event = maybeEvent else { - return XCTFail("Expected to have an event") - } - - XCTAssertEqual(event.id, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c") - XCTAssertEqual(event.source, "aws.events") - XCTAssertEqual(event.accountId, "123456789012") - XCTAssertEqual(event.time, Date(timeIntervalSince1970: 0)) - XCTAssertEqual(event.region, .us_east_1) - XCTAssertEqual(event.resources, ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"]) - XCTAssertEqual(event.detail.instanceId, "0") - XCTAssertEqual(event.detail.state, .stopping) - } - - func testEC2SpotInstanceInterruptionNoticeEventFromJSON() { - let eventBody = CloudwatchTests.eventBody(type: Cloudwatch.EC2.SpotInstanceInterruptionNotice.name, - details: "{ \"instance-id\": \"0\", \"instance-action\": \"terminate\" }") - let data = eventBody.data(using: .utf8)! - var maybeEvent: Cloudwatch.EC2.SpotInstanceInterruptionNoticeEvent? - XCTAssertNoThrow(maybeEvent = try JSONDecoder().decode(Cloudwatch.EC2.SpotInstanceInterruptionNoticeEvent.self, from: data)) - - guard let event = maybeEvent else { - return XCTFail("Expected to have an event") - } - - XCTAssertEqual(event.id, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c") - XCTAssertEqual(event.source, "aws.events") - XCTAssertEqual(event.accountId, "123456789012") - XCTAssertEqual(event.time, Date(timeIntervalSince1970: 0)) - XCTAssertEqual(event.region, .us_east_1) - XCTAssertEqual(event.resources, ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"]) - XCTAssertEqual(event.detail.instanceId, "0") - XCTAssertEqual(event.detail.action, .terminate) - } - - func testCustomEventFromJSON() { - struct Custom: CloudwatchDetail { - public static let name = "Custom" - - let name: String - } - - let eventBody = CloudwatchTests.eventBody(type: Custom.name, details: "{ \"name\": \"foo\" }") - let data = eventBody.data(using: .utf8)! - var maybeEvent: Cloudwatch.Event? - XCTAssertNoThrow(maybeEvent = try JSONDecoder().decode(Cloudwatch.Event.self, from: data)) - - guard let event = maybeEvent else { - return XCTFail("Expected to have an event") - } - - XCTAssertEqual(event.id, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c") - XCTAssertEqual(event.source, "aws.events") - XCTAssertEqual(event.accountId, "123456789012") - XCTAssertEqual(event.time, Date(timeIntervalSince1970: 0)) - XCTAssertEqual(event.region, .us_east_1) - XCTAssertEqual(event.resources, ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"]) - XCTAssertEqual(event.detail.name, "foo") - } - - func testUnregistredType() { - let eventBody = CloudwatchTests.eventBody(type: UUID().uuidString, details: "{}") - let data = eventBody.data(using: .utf8)! - XCTAssertThrowsError(try JSONDecoder().decode(Cloudwatch.ScheduledEvent.self, from: data)) { error in - XCTAssert(error is Cloudwatch.DetailTypeMismatch, "expected DetailTypeMismatch but received \(error)") - } - } - - func testTypeMismatch() { - let eventBody = CloudwatchTests.eventBody(type: Cloudwatch.EC2.InstanceStateChangeNotification.name, - details: "{ \"instance-id\": \"0\", \"state\": \"stopping\" }") - let data = eventBody.data(using: .utf8)! - XCTAssertThrowsError(try JSONDecoder().decode(Cloudwatch.ScheduledEvent.self, from: data)) { error in - XCTAssert(error is Cloudwatch.DetailTypeMismatch, "expected DetailTypeMismatch but received \(error)") - } - } -} diff --git a/Tests/AWSLambdaEventsTests/DynamoDBTests.swift b/Tests/AWSLambdaEventsTests/DynamoDBTests.swift deleted file mode 100644 index 91745ff6..00000000 --- a/Tests/AWSLambdaEventsTests/DynamoDBTests.swift +++ /dev/null @@ -1,232 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 DynamoDBTests: XCTestCase { - static let streamEventBody = """ - { - "Records": [ - { - "eventID": "1", - "eventVersion": "1.0", - "dynamodb": { - "ApproximateCreationDateTime": 1.578648338E9, - "Keys": { - "Id": { - "N": "101" - } - }, - "NewImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES", - "SequenceNumber": "111", - "SizeBytes": 26 - }, - "awsRegion": "eu-central-1", - "eventName": "INSERT", - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "2", - "eventVersion": "1.0", - "dynamodb": { - "ApproximateCreationDateTime": 1.578648338E9, - "OldImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "SequenceNumber": "222", - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 59, - "NewImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": "eu-central-1", - "eventName": "MODIFY", - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "3", - "eventVersion": "1.0", - "dynamodb": { - "ApproximateCreationDateTime":1.578648338E9, - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 38, - "SequenceNumber": "333", - "OldImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": "eu-central-1", - "eventName": "REMOVE", - "eventSourceARN": "arn:aws:dynamodb:eu-central-1:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - } - ] - } - """ - - func testEventFromJSON() { - let data = DynamoDBTests.streamEventBody.data(using: .utf8)! - var event: DynamoDB.Event? - XCTAssertNoThrow(event = try JSONDecoder().decode(DynamoDB.Event.self, from: data)) - - XCTAssertEqual(event?.records.count, 3) - } - - // MARK: - Parse Attribute Value Tests - - - func testAttributeValueBoolDecoding() { - let json = "{\"BOOL\": true}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .boolean(true)) - } - - func testAttributeValueBinaryDecoding() { - let json = "{\"B\": \"YmFzZTY0\"}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .binary([UInt8]("base64".utf8))) - } - - func testAttributeValueBinarySetDecoding() { - let json = "{\"BS\": [\"YmFzZTY0\", \"YWJjMTIz\"]}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .binarySet([[UInt8]("base64".utf8), [UInt8]("abc123".utf8)])) - } - - func testAttributeValueStringDecoding() { - let json = "{\"S\": \"huhu\"}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .string("huhu")) - } - - func testAttributeValueStringSetDecoding() { - let json = "{\"SS\": [\"huhu\", \"haha\"]}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .stringSet(["huhu", "haha"])) - } - - func testAttributeValueNullDecoding() { - let json = "{\"NULL\": true}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .null) - } - - func testAttributeValueNumberDecoding() { - let json = "{\"N\": \"1.2345\"}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .number("1.2345")) - } - - func testAttributeValueNumberSetDecoding() { - let json = "{\"NS\": [\"1.2345\", \"-19\"]}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .numberSet(["1.2345", "-19"])) - } - - func testAttributeValueListDecoding() { - let json = "{\"L\": [{\"NS\": [\"1.2345\", \"-19\"]}, {\"S\": \"huhu\"}]}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .list([.numberSet(["1.2345", "-19"]), .string("huhu")])) - } - - func testAttributeValueMapDecoding() { - let json = "{\"M\": {\"numbers\": {\"NS\": [\"1.2345\", \"-19\"]}, \"string\": {\"S\": \"huhu\"}}}" - var value: DynamoDB.AttributeValue? - XCTAssertNoThrow(value = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) - XCTAssertEqual(value, .map([ - "numbers": .numberSet(["1.2345", "-19"]), - "string": .string("huhu"), - ])) - } - - func testAttributeValueEmptyDecoding() { - let json = "{\"haha\": 1}" - XCTAssertThrowsError(_ = try JSONDecoder().decode(DynamoDB.AttributeValue.self, from: json.data(using: .utf8)!)) { error in - guard case DecodingError.dataCorrupted = error else { - XCTFail("Unexpected error: \(String(describing: error))") - return - } - } - } - - func testAttributeValueEquatable() { - XCTAssertEqual(DynamoDB.AttributeValue.boolean(true), .boolean(true)) - XCTAssertNotEqual(DynamoDB.AttributeValue.boolean(true), .boolean(false)) - XCTAssertNotEqual(DynamoDB.AttributeValue.boolean(true), .string("haha")) - } - - // MARK: - DynamoDB Decoder Tests - - - func testDecoderSimple() { - let value: [String: DynamoDB.AttributeValue] = [ - "foo": .string("bar"), - "xyz": .number("123"), - ] - - struct Test: Codable { - let foo: String - let xyz: UInt8 - } - - var test: Test? - XCTAssertNoThrow(test = try DynamoDB.Decoder().decode(Test.self, from: value)) - XCTAssertEqual(test?.foo, "bar") - XCTAssertEqual(test?.xyz, 123) - } -} diff --git a/Tests/AWSLambdaEventsTests/S3Tests.swift b/Tests/AWSLambdaEventsTests/S3Tests.swift deleted file mode 100644 index 5c6d240c..00000000 --- a/Tests/AWSLambdaEventsTests/S3Tests.swift +++ /dev/null @@ -1,158 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 eventBodyObjectCreated = """ - { - "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" - } - } - } - ] - } - """ - - // A S3 ObjectRemoved:* event does not contain the object size - static let eventBodyObjectRemoved = """ - { - "Records": [ - { - "eventVersion":"2.1", - "eventSource":"aws:s3", - "awsRegion":"eu-central-1", - "eventTime":"2020-01-13T09:25:40.621Z", - "eventName":"ObjectRemoved:DeleteMarkerCreated", - "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", - "eTag":"91a7f2c3ae81bcc6afef83979b463f0e", - "sequencer":"005E1C37948E783A6E" - } - } - } - ] - } - """ - - func testObjectCreatedEvent() { - let data = S3Tests.eventBodyObjectCreated.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") - } - - func testObjectRemovedEvent() { - let data = S3Tests.eventBodyObjectRemoved.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, "ObjectRemoved:DeleteMarkerCreated") - 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") - XCTAssertNil(record.s3.object.size) - XCTAssertEqual(record.s3.object.eTag, "91a7f2c3ae81bcc6afef83979b463f0e") - XCTAssertEqual(record.s3.object.sequencer, "005E1C37948E783A6E") - } -} diff --git a/Tests/AWSLambdaEventsTests/SESTests.swift b/Tests/AWSLambdaEventsTests/SESTests.swift deleted file mode 100644 index 0f4b417d..00000000 --- a/Tests/AWSLambdaEventsTests/SESTests.swift +++ /dev/null @@ -1,128 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 SESTests: XCTestCase { - static let eventBody = """ - { - "Records": [ - { - "eventSource": "aws:ses", - "eventVersion": "1.0", - "ses": { - "mail": { - "commonHeaders": { - "date": "Wed, 7 Oct 2015 12:34:56 -0700", - "from": [ - "Jane Doe " - ], - "messageId": "<0123456789example.com>", - "returnPath": "janedoe@example.com", - "subject": "Test Subject", - "to": [ - "johndoe@example.com" - ] - }, - "destination": [ - "johndoe@example.com" - ], - "headers": [ - { - "name": "Return-Path", - "value": "" - }, - { - "name": "Received", - "value": "from mailer.example.com (mailer.example.com [203.0.113.1]) by inbound-smtp.eu-west-1.amazonaws.com with SMTP id o3vrnil0e2ic28trm7dfhrc2v0cnbeccl4nbp0g1 for johndoe@example.com; Wed, 07 Oct 2015 12:34:56 +0000 (UTC)" - } - ], - "headersTruncated": true, - "messageId": "5h5auqp1oa1bg49b2q8f8tmli1oju8pcma2haao1", - "source": "janedoe@example.com", - "timestamp": "1970-01-01T00:00:00.000Z" - }, - "receipt": { - "action": { - "functionArn": "arn:aws:lambda:eu-west-1:123456789012:function:Example", - "invocationType": "Event", - "type": "Lambda" - }, - "dkimVerdict": { - "status": "PASS" - }, - "processingTimeMillis": 574, - "recipients": [ - "test@swift-server.com", - "test2@swift-server.com" - ], - "spamVerdict": { - "status": "PASS" - }, - "spfVerdict": { - "status": "PROCESSING_FAILED" - }, - "timestamp": "1970-01-01T00:00:00.000Z", - "virusVerdict": { - "status": "FAIL" - } - } - } - } - ] - } - """ - - func testSimpleEventFromJSON() { - let data = Data(SESTests.eventBody.utf8) - var event: SES.Event? - XCTAssertNoThrow(event = try JSONDecoder().decode(SES.Event.self, from: data)) - - guard let record = event?.records.first else { - XCTFail("Expected to have one record") - return - } - - XCTAssertEqual(record.eventSource, "aws:ses") - XCTAssertEqual(record.eventVersion, "1.0") - XCTAssertEqual(record.ses.mail.commonHeaders.date.description, "2015-10-07 19:34:56 +0000") - XCTAssertEqual(record.ses.mail.commonHeaders.from[0], "Jane Doe ") - XCTAssertEqual(record.ses.mail.commonHeaders.messageId, "<0123456789example.com>") - XCTAssertEqual(record.ses.mail.commonHeaders.returnPath, "janedoe@example.com") - XCTAssertEqual(record.ses.mail.commonHeaders.subject, "Test Subject") - XCTAssertEqual(record.ses.mail.commonHeaders.to?[0], "johndoe@example.com") - XCTAssertEqual(record.ses.mail.destination[0], "johndoe@example.com") - XCTAssertEqual(record.ses.mail.headers[0].name, "Return-Path") - XCTAssertEqual(record.ses.mail.headers[0].value, "") - XCTAssertEqual(record.ses.mail.headers[1].name, "Received") - XCTAssertEqual(record.ses.mail.headers[1].value, "from mailer.example.com (mailer.example.com [203.0.113.1]) by inbound-smtp.eu-west-1.amazonaws.com with SMTP id o3vrnil0e2ic28trm7dfhrc2v0cnbeccl4nbp0g1 for johndoe@example.com; Wed, 07 Oct 2015 12:34:56 +0000 (UTC)") - XCTAssertEqual(record.ses.mail.headersTruncated, true) - XCTAssertEqual(record.ses.mail.messageId, "5h5auqp1oa1bg49b2q8f8tmli1oju8pcma2haao1") - XCTAssertEqual(record.ses.mail.source, "janedoe@example.com") - XCTAssertEqual(record.ses.mail.timestamp.description, "1970-01-01 00:00:00 +0000") - - XCTAssertEqual(record.ses.receipt.action.functionArn, "arn:aws:lambda:eu-west-1:123456789012:function:Example") - XCTAssertEqual(record.ses.receipt.action.invocationType, "Event") - XCTAssertEqual(record.ses.receipt.action.type, "Lambda") - XCTAssertEqual(record.ses.receipt.dkimVerdict.status, .pass) - XCTAssertEqual(record.ses.receipt.processingTimeMillis, 574) - XCTAssertEqual(record.ses.receipt.recipients[0], "test@swift-server.com") - XCTAssertEqual(record.ses.receipt.recipients[1], "test2@swift-server.com") - XCTAssertEqual(record.ses.receipt.spamVerdict.status, .pass) - XCTAssertEqual(record.ses.receipt.spfVerdict.status, .processingFailed) - XCTAssertEqual(record.ses.receipt.timestamp.description, "1970-01-01 00:00:00 +0000") - XCTAssertEqual(record.ses.receipt.virusVerdict.status, .fail) - } -} diff --git a/Tests/AWSLambdaEventsTests/SNSTests.swift b/Tests/AWSLambdaEventsTests/SNSTests.swift deleted file mode 100644 index 916a69ea..00000000 --- a/Tests/AWSLambdaEventsTests/SNSTests.swift +++ /dev/null @@ -1,82 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 SNSTests: XCTestCase { - static let eventBody = """ - { - "Records": [ - { - "EventSource": "aws:sns", - "EventVersion": "1.0", - "EventSubscriptionArn": "arn:aws:sns:eu-central-1:079477498937:EventSources-SNSTopic-1NHENSE2MQKF5:6fabdb7f-b27e-456d-8e8a-14679db9e40c", - "Sns": { - "Type": "Notification", - "MessageId": "bdb6900e-1ae9-5b4b-b7fc-c681fde222e3", - "TopicArn": "arn:aws:sns:eu-central-1:079477498937:EventSources-SNSTopic-1NHENSE2MQKF5", - "Subject": null, - "Message": "{\\\"hello\\\": \\\"world\\\"}", - "Timestamp": "2020-01-08T14:18:51.203Z", - "SignatureVersion": "1", - "Signature": "LJMF/xmMH7A1gNy2unLA3hmzyf6Be+zS/Yeiiz9tZbu6OG8fwvWZeNOcEZardhSiIStc0TF7h9I+4Qz3omCntaEfayzTGmWN8itGkn2mfn/hMFmPbGM8gEUz3+jp1n6p+iqP3XTx92R0LBIFrU3ylOxSo8+SCOjA015M93wfZzwj0WPtynji9iAvvtf15d8JxPUu1T05BRitpFd5s6ZXDHtVQ4x/mUoLUN8lOVp+rs281/ZdYNUG/V5CwlyUDTOERdryTkBJ/GO1NNPa+6m04ywJFa5d+BC8mDcUcHhhXXjpTEbt8AHBmswK3nudHrVMRO/G4zmssxU2P7ii5+gCfA==", - "SigningCertUrl": "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-6aad65c2f9911b05cd53efda11f913f9.pem", - "UnsubscribeUrl": "https://sns.eu-central-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-central-1:079477498937:EventSources-SNSTopic-1NHENSE2MQKF5:6fabdb7f-b27e-456d-8e8a-14679db9e40c", - "MessageAttributes": { - "binary":{ - "Type": "Binary", - "Value": "YmFzZTY0" - }, - "string":{ - "Type": "String", - "Value": "abc123" - } - } - } - } - ] - } - """ - - func testSimpleEventFromJSON() { - let data = SNSTests.eventBody.data(using: .utf8)! - var event: SNS.Event? - XCTAssertNoThrow(event = try JSONDecoder().decode(SNS.Event.self, from: data)) - - guard let record = event?.records.first else { - XCTFail("Expected to have one record") - return - } - - XCTAssertEqual(record.eventSource, "aws:sns") - XCTAssertEqual(record.eventVersion, "1.0") - XCTAssertEqual(record.eventSubscriptionArn, "arn:aws:sns:eu-central-1:079477498937:EventSources-SNSTopic-1NHENSE2MQKF5:6fabdb7f-b27e-456d-8e8a-14679db9e40c") - - XCTAssertEqual(record.sns.type, "Notification") - XCTAssertEqual(record.sns.messageId, "bdb6900e-1ae9-5b4b-b7fc-c681fde222e3") - XCTAssertEqual(record.sns.topicArn, "arn:aws:sns:eu-central-1:079477498937:EventSources-SNSTopic-1NHENSE2MQKF5") - XCTAssertEqual(record.sns.message, "{\"hello\": \"world\"}") - XCTAssertEqual(record.sns.timestamp, Date(timeIntervalSince1970: 1_578_493_131.203)) - XCTAssertEqual(record.sns.signatureVersion, "1") - XCTAssertEqual(record.sns.signature, "LJMF/xmMH7A1gNy2unLA3hmzyf6Be+zS/Yeiiz9tZbu6OG8fwvWZeNOcEZardhSiIStc0TF7h9I+4Qz3omCntaEfayzTGmWN8itGkn2mfn/hMFmPbGM8gEUz3+jp1n6p+iqP3XTx92R0LBIFrU3ylOxSo8+SCOjA015M93wfZzwj0WPtynji9iAvvtf15d8JxPUu1T05BRitpFd5s6ZXDHtVQ4x/mUoLUN8lOVp+rs281/ZdYNUG/V5CwlyUDTOERdryTkBJ/GO1NNPa+6m04ywJFa5d+BC8mDcUcHhhXXjpTEbt8AHBmswK3nudHrVMRO/G4zmssxU2P7ii5+gCfA==") - XCTAssertEqual(record.sns.signingCertURL, "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-6aad65c2f9911b05cd53efda11f913f9.pem") - XCTAssertEqual(record.sns.unsubscribeUrl, "https://sns.eu-central-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-central-1:079477498937:EventSources-SNSTopic-1NHENSE2MQKF5:6fabdb7f-b27e-456d-8e8a-14679db9e40c") - - XCTAssertEqual(record.sns.messageAttributes?.count, 2) - - XCTAssertEqual(record.sns.messageAttributes?["binary"], .binary([UInt8]("base64".utf8))) - XCTAssertEqual(record.sns.messageAttributes?["string"], .string("abc123")) - } -} diff --git a/Tests/AWSLambdaEventsTests/SQSTests.swift b/Tests/AWSLambdaEventsTests/SQSTests.swift deleted file mode 100644 index ca8e3c70..00000000 --- a/Tests/AWSLambdaEventsTests/SQSTests.swift +++ /dev/null @@ -1,87 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 SQSTests: XCTestCase { - static let eventBody = """ - { - "Records": [ - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", - "receiptHandle": "MessageReceiptHandle", - "body": "Hello from SQS!", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": { - "number":{ - "stringValue":"123", - "stringListValues":[], - "binaryListValues":[], - "dataType":"Number" - }, - "string":{ - "stringValue":"abc123", - "stringListValues":[], - "binaryListValues":[], - "dataType":"String" - }, - "binary":{ - "dataType": "Binary", - "stringListValues":[], - "binaryListValues":[], - "binaryValue":"YmFzZTY0" - }, - - }, - "md5OfBody": "7b270e59b47ff90a553787216d55d91d", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", - "awsRegion": "us-east-1" - } - ] - } - """ - - func testSimpleEventFromJSON() { - let data = SQSTests.eventBody.data(using: .utf8)! - var event: SQS.Event? - XCTAssertNoThrow(event = try JSONDecoder().decode(SQS.Event.self, from: data)) - - guard let message = event?.records.first else { - XCTFail("Expected to have one message in the event") - return - } - - XCTAssertEqual(message.messageId, "19dd0b57-b21e-4ac1-bd88-01bbb068cb78") - XCTAssertEqual(message.receiptHandle, "MessageReceiptHandle") - XCTAssertEqual(message.body, "Hello from SQS!") - XCTAssertEqual(message.attributes.count, 4) - - XCTAssertEqual(message.messageAttributes, [ - "number": .number("123"), - "string": .string("abc123"), - "binary": .binary([UInt8]("base64".utf8)), - ]) - XCTAssertEqual(message.md5OfBody, "7b270e59b47ff90a553787216d55d91d") - XCTAssertEqual(message.eventSource, "aws:sqs") - XCTAssertEqual(message.eventSourceArn, "arn:aws:sqs:us-east-1:123456789012:MyQueue") - XCTAssertEqual(message.awsRegion, .us_east_1) - } -} diff --git a/Tests/AWSLambdaEventsTests/Utils/Base64Tests.swift b/Tests/AWSLambdaEventsTests/Utils/Base64Tests.swift deleted file mode 100644 index 59e300c1..00000000 --- a/Tests/AWSLambdaEventsTests/Utils/Base64Tests.swift +++ /dev/null @@ -1,72 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 Base64Tests: XCTestCase { - // MARK: - Decoding - - - func testDecodeEmptyString() throws { - var decoded: [UInt8]? - XCTAssertNoThrow(decoded = try "".base64decoded()) - XCTAssertEqual(decoded?.count, 0) - } - - func testBase64DecodingArrayOfNulls() throws { - let expected = Array(repeating: UInt8(0), count: 10) - var decoded: [UInt8]? - XCTAssertNoThrow(decoded = try "AAAAAAAAAAAAAA==".base64decoded()) - XCTAssertEqual(decoded, expected) - } - - func testBase64DecodingAllTheBytesSequentially() { - let base64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==" - - let expected = Array(UInt8(0) ... UInt8(255)) - var decoded: [UInt8]? - XCTAssertNoThrow(decoded = try base64.base64decoded()) - - XCTAssertEqual(decoded, expected) - } - - func testBase64UrlDecodingAllTheBytesSequentially() { - let base64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w==" - - let expected = Array(UInt8(0) ... UInt8(255)) - var decoded: [UInt8]? - XCTAssertNoThrow(decoded = try base64.base64decoded(options: .base64UrlAlphabet)) - - XCTAssertEqual(decoded, expected) - } - - func testBase64DecodingWithPoop() { - XCTAssertThrowsError(_ = try "💩".base64decoded()) { error in - XCTAssertEqual(error as? Base64.DecodingError, .invalidCharacter(240)) - } - } - - func testBase64DecodingWithInvalidLength() { - XCTAssertThrowsError(_ = try "AAAAA".base64decoded()) { error in - XCTAssertEqual(error as? Base64.DecodingError, .invalidLength) - } - } - - func testNSStringToDecode() { - let test = "1234567" - let nsstring = test.data(using: .utf8)!.base64EncodedString() - - XCTAssertNoThrow(try nsstring.base64decoded()) - } -} diff --git a/Tests/AWSLambdaEventsTests/Utils/DateWrapperTests.swift b/Tests/AWSLambdaEventsTests/Utils/DateWrapperTests.swift deleted file mode 100644 index c8d2eebc..00000000 --- a/Tests/AWSLambdaEventsTests/Utils/DateWrapperTests.swift +++ /dev/null @@ -1,140 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 DateWrapperTests: XCTestCase { - func testISO8601CodingWrapperSuccess() { - struct TestEvent: Decodable { - @ISO8601Coding - var date: Date - } - - let json = #"{"date":"2020-03-26T16:53:05Z"}"# - var event: TestEvent? - XCTAssertNoThrow(event = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) - - XCTAssertEqual(event?.date, Date(timeIntervalSince1970: 1_585_241_585)) - } - - func testISO8601CodingWrapperFailure() { - struct TestEvent: Decodable { - @ISO8601Coding - var date: Date - } - - let date = "2020-03-26T16:53:05" // missing Z at end - let json = #"{"date":"\#(date)"}"# - XCTAssertThrowsError(_ = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) { error in - guard case DecodingError.dataCorrupted(let context) = error else { - XCTFail("Unexpected error: \(error)"); return - } - - XCTAssertEqual(context.codingPath.map(\.stringValue), ["date"]) - XCTAssertEqual(context.debugDescription, "Expected date to be in ISO8601 date format, but `\(date)` is not in the correct format") - XCTAssertNil(context.underlyingError) - } - } - - func testISO8601WithFractionalSecondsCodingWrapperSuccess() { - struct TestEvent: Decodable { - @ISO8601WithFractionalSecondsCoding - var date: Date - } - - let json = #"{"date":"2020-03-26T16:53:05.123Z"}"# - var event: TestEvent? - XCTAssertNoThrow(event = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) - - XCTAssertEqual(event?.date, Date(timeIntervalSince1970: 1_585_241_585.123)) - } - - func testISO8601WithFractionalSecondsCodingWrapperFailure() { - struct TestEvent: Decodable { - @ISO8601WithFractionalSecondsCoding - var date: Date - } - - let date = "2020-03-26T16:53:05Z" // missing fractional seconds - let json = #"{"date":"\#(date)"}"# - XCTAssertThrowsError(_ = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) { error in - guard case DecodingError.dataCorrupted(let context) = error else { - XCTFail("Unexpected error: \(error)"); return - } - - XCTAssertEqual(context.codingPath.map(\.stringValue), ["date"]) - XCTAssertEqual(context.debugDescription, "Expected date to be in ISO8601 date format with fractional seconds, but `\(date)` is not in the correct format") - XCTAssertNil(context.underlyingError) - } - } - - func testRFC5322DateTimeCodingWrapperSuccess() { - struct TestEvent: Decodable { - @RFC5322DateTimeCoding - var date: Date - } - - let json = #"{"date":"Thu, 5 Apr 2012 23:47:37 +0200"}"# - var event: TestEvent? - XCTAssertNoThrow(event = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) - - XCTAssertEqual(event?.date.description, "2012-04-05 21:47:37 +0000") - } - - func testRFC5322DateTimeCodingWrapperWithExtraTimeZoneSuccess() { - struct TestEvent: Decodable { - @RFC5322DateTimeCoding - var date: Date - } - - let json = #"{"date":"Fri, 26 Jun 2020 03:04:03 -0500 (CDT)"}"# - var event: TestEvent? - XCTAssertNoThrow(event = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) - - XCTAssertEqual(event?.date.description, "2020-06-26 08:04:03 +0000") - } - - func testRFC5322DateTimeCodingWrapperWithAlphabeticTimeZoneSuccess() { - struct TestEvent: Decodable { - @RFC5322DateTimeCoding - var date: Date - } - - let json = #"{"date":"Fri, 26 Jun 2020 03:04:03 CDT"}"# - var event: TestEvent? - XCTAssertNoThrow(event = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) - - XCTAssertEqual(event?.date.description, "2020-06-26 08:04:03 +0000") - } - - func testRFC5322DateTimeCodingWrapperFailure() { - struct TestEvent: Decodable { - @RFC5322DateTimeCoding - var date: Date - } - - let date = "Thu, 5 Apr 2012 23:47 +0200" // missing seconds - let json = #"{"date":"\#(date)"}"# - XCTAssertThrowsError(_ = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) { error in - guard case DecodingError.dataCorrupted(let context) = error else { - XCTFail("Unexpected error: \(error)"); return - } - - XCTAssertEqual(context.codingPath.map(\.stringValue), ["date"]) - XCTAssertEqual(context.debugDescription, "Expected date to be in RFC5322 date-time format with fractional seconds, but `\(date)` is not in the correct format") - XCTAssertNil(context.underlyingError) - } - } -} diff --git a/readme.md b/readme.md index cf1981a1..a4e5238f 100644 --- a/readme.md +++ b/readme.md @@ -10,17 +10,6 @@ Combine this with Swift's developer friendliness, expressiveness, and emphasis o Swift AWS Lambda Runtime was designed to make building Lambda functions in Swift simple and safe. The library is an implementation of the [AWS Lambda Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) and uses an embedded asynchronous HTTP Client based on [SwiftNIO](http://github.com/apple/swift-nio) that is fine-tuned for performance in the AWS Runtime context. The library provides a multi-tier API that allows building a range of Lambda functions: From quick and simple closures to complex, performance-sensitive event handlers. -## Project status - -This is the beginning of a community-driven open-source project actively seeking contributions. -While the core API is considered stable, the API may still evolve as we get closer to a `1.0` version. -There are several areas which need additional attention, including but not limited to: - -* Further performance tuning -* Additional trigger events -* Additional documentation and best practices -* Additional examples - ## Getting started If you have never used AWS Lambda or Docker before, check out this [getting started guide](https://fabianfett.de/getting-started-with-swift-aws-lambda-runtime) which helps you with every step from zero to a running Lambda. @@ -86,7 +75,33 @@ Next, create a `main.swift` and implement your Lambda. } ``` - Since most Lambda functions are triggered by events originating in the AWS platform like `SNS`, `SQS` or `APIGateway`, the package also includes a `AWSLambdaEvents` module that provides implementations for most common AWS event types further simplifying writing Lambda functions. For example, handling an `SQS` message: + Since most Lambda functions are triggered by events originating in the AWS platform like `SNS`, `SQS` or `APIGateway`, the [Swift AWS Lambda Events](http://github.com/swift-server/swift-aws-lambda-events) package includes a `AWSLambdaEvents` module that provides implementations for most common AWS event types further simplifying writing Lambda functions. For example, handling an `SQS` message: + +First, add a dependency on the event packages: + + ```swift + // swift-tools-version:5.2 + + import PackageDescription + + let package = Package( + name: "my-lambda", + products: [ + .executable(name: "MyLambda", targets: ["MyLambda"]), + ], + dependencies: [ + .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "0.1.0"), + .package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "0.1.0"), + ], + targets: [ + .target(name: "MyLambda", dependencies: [ + .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), + .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"), + ]), + ] + ) + ``` + ```swift // Import the modules @@ -333,16 +348,7 @@ By default, the library also registers a Signal handler that traps `INT` and `TE ### Integration with AWS Platform Events -AWS Lambda functions can be invoked directly from the AWS Lambda console UI, AWS Lambda API, AWS SDKs, AWS CLI, and AWS toolkits. More commonly, they are invoked as a reaction to an events coming from the AWS platform. To make it easier to integrate with AWS platform events, the library includes an `AWSLambdaEvents` target which provides abstractions for many commonly used events. Additional events can be easily modeled when needed following the same patterns set by `AWSLambdaEvents`. Integration points with the AWS Platform include: - -* [APIGateway Proxy](https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html) -* [S3 Events](https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html) -* [SES Events](https://docs.aws.amazon.com/lambda/latest/dg/services-ses.html) -* [SNS Events](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) -* [SQS Events](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html) -* [CloudWatch Events](https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html) - -**Note**: Each one of the integration points mentioned above includes a set of `Codable` structs that mirror AWS' data model for these APIs. +AWS Lambda functions can be invoked directly from the AWS Lambda console UI, AWS Lambda API, AWS SDKs, AWS CLI, and AWS toolkits. More commonly, they are invoked as a reaction to an events coming from the AWS platform. To make it easier to integrate with AWS platform events, [Swift AWS Lambda Runtime Events](http://github.com/swift-server/swift-aws-lambda-events) library is available, designed to work together with this runtime library. [Swift AWS Lambda Runtime Events](http://github.com/swift-server/swift-aws-lambda-events) includes an `AWSLambdaEvents` target which provides abstractions for many commonly used events. ## Performance @@ -359,3 +365,13 @@ Swift provides great Unicode support via [ICU](http://site.icu-project.org/home) ## Security Please see [SECURITY.md](SECURITY.md) for details on the security process. + +## Project status + +This is a community-driven open-source project actively seeking contributions. +While the core API is considered stable, the API may still evolve as we get closer to a `1.0` version. +There are several areas which need additional attention, including but not limited to: + +* Further performance tuning +* Additional documentation and best practices +* Additional examples