mirror of
https://github.com/swift-server/swift-aws-lambda-runtime.git
synced 2026-05-03 07:22:27 +00:00
split events into spearate package (#216)
motivation: the runtime library has a stable API while the events are still moving target. In order to provide a 1.0 stable version we separate them out changes: * remove Events module * update readme * update Samples * remove gateway example Co-authored-by: Fabian Fett <fabianfett@apple.com>
This commit is contained in:
@@ -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"),
|
||||
]),
|
||||
|
||||
@@ -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<APIGateway.V2.Response> {
|
||||
context.logger.debug("hello, api gateway!")
|
||||
return context.eventLoop.makeSucceededFuture(APIGateway.V2.Response(statusCode: .ok, body: "hello, world!"))
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<ResultType: Encodable>: 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<String>
|
||||
}
|
||||
@@ -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<Detail: CloudwatchDetail>: 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<Scheduled>
|
||||
public struct Scheduled: CloudwatchDetail {
|
||||
public static let name = "Scheduled Event"
|
||||
}
|
||||
|
||||
public enum EC2 {
|
||||
public typealias InstanceStateChangeNotificationEvent = Event<InstanceStateChangeNotification>
|
||||
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<SpotInstanceInterruptionNotice>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<T: Decodable>(_ type: T.Type, from image: [String: AttributeValue])
|
||||
throws -> T {
|
||||
try self.decode(type, from: .map(image))
|
||||
}
|
||||
|
||||
@inlinable public func decode<T: Decodable>(_ 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: Decodable>(_: T.Type) throws -> T {
|
||||
try T(from: self)
|
||||
}
|
||||
|
||||
@usableFromInline func container<Key>(keyedBy type: Key.Type) throws ->
|
||||
KeyedDecodingContainer<Key> 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<Key>(
|
||||
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<K: CodingKey>: 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<T>(_ type: T.Type, forKey key: K) throws -> T where T: Decodable {
|
||||
let decoder = try self.decoderForKey(key)
|
||||
return try T(from: decoder)
|
||||
}
|
||||
|
||||
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: K) throws
|
||||
-> KeyedDecodingContainer<NestedKey> 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<T: FixedWidthInteger>(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<T: LosslessStringConvertible>(
|
||||
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>(_: 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<T: FixedWidthInteger>() 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<T: LosslessStringConvertible>()
|
||||
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>(_: 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<NestedKey>(keyedBy type: NestedKey.Type) throws
|
||||
-> KeyedDecodingContainer<NestedKey> 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<T: FixedWidthInteger>() 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<T: LosslessStringConvertible>()
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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`.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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`.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Buffer: Collection>(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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Custom>?
|
||||
XCTAssertNoThrow(maybeEvent = try JSONDecoder().decode(Cloudwatch.Event<Custom>.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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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 <janedoe@example.com>"
|
||||
],
|
||||
"messageId": "<0123456789example.com>",
|
||||
"returnPath": "janedoe@example.com",
|
||||
"subject": "Test Subject",
|
||||
"to": [
|
||||
"johndoe@example.com"
|
||||
]
|
||||
},
|
||||
"destination": [
|
||||
"johndoe@example.com"
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"name": "Return-Path",
|
||||
"value": "<janedoe@example.com>"
|
||||
},
|
||||
{
|
||||
"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 <janedoe@example.com>")
|
||||
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, "<janedoe@example.com>")
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user