From c1af9de7dfd2a25b6465563f5891d824c810ed1d Mon Sep 17 00:00:00 2001 From: Richard Kendall Wolf Date: Sun, 23 Jul 2023 16:19:07 -0500 Subject: [PATCH] Add WebSockets event types (and tests) --- .../APIGateway+WebSockets.swift | 68 ++++++++++++++++ .../APIGateway+WebsocketsTests.swift | 77 +++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 Sources/AWSLambdaEvents/APIGateway+WebSockets.swift create mode 100644 Tests/AWSLambdaEventsTests/APIGateway+WebsocketsTests.swift diff --git a/Sources/AWSLambdaEvents/APIGateway+WebSockets.swift b/Sources/AWSLambdaEvents/APIGateway+WebSockets.swift new file mode 100644 index 0000000..d9bcef6 --- /dev/null +++ b/Sources/AWSLambdaEvents/APIGateway+WebSockets.swift @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) YEARS 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 +// +//===----------------------------------------------------------------------===// + +/// `APIGatewayWebSocketRequest` is a variation of the`APIGatewayV2Request` +/// and contains data coming from the WebSockets API Gateway. +public struct APIGatewayWebSocketRequest: Codable { + /// `Context` contains information to identify the AWS account and resources invoking the Lambda function. + public struct Context: Codable { + public struct Identity: Codable { + let sourceIp: String + } + + let routeKey: String + let eventType: String + let extendedRequestId: String + /// The request time in format: 23/Apr/2020:11:08:18 +0000 + let requestTime: String + let messageDirection: String + let stage: String + let connectedAt: UInt64 + let requestTimeEpoch: UInt64 + let identity: Identity + let requestId: String + let domainName: String + let connectionId: String + let apiId: String + } + + public let headers: HTTPHeaders? + public let multiValueHeaders: HTTPMultiValueHeaders? + public let context: Context + public let body: String? + public let isBase64Encoded: Bool? + + enum CodingKeys: String, CodingKey { + case headers + case multiValueHeaders + case context = "requestContext" + case body + case isBase64Encoded + } +} + +/// `APIGatewayWebSocketResponse` is a type alias for `APIGatewayV2Request`. +/// Typically, lambda WebSockets servers send clients data via +/// the ApiGatewayManagementApi mechanism. However, APIGateway does require +/// lambda servers to return some kind of status when APIGateway invokes them. +/// This can be as simple as always returning a 200 "OK" response for all +/// WebSockets requests (the ApiGatewayManagementApi can return any errors to +/// WebSockets clients). +public typealias APIGatewayWebSocketResponse = APIGatewayV2Response + +#if swift(>=5.6) +extension APIGatewayWebSocketRequest: Sendable {} +extension APIGatewayWebSocketRequest.Context: Sendable {} +extension APIGatewayWebSocketRequest.Context.Identity: Sendable {} +#endif diff --git a/Tests/AWSLambdaEventsTests/APIGateway+WebsocketsTests.swift b/Tests/AWSLambdaEventsTests/APIGateway+WebsocketsTests.swift new file mode 100644 index 0000000..19402b7 --- /dev/null +++ b/Tests/AWSLambdaEventsTests/APIGateway+WebsocketsTests.swift @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) YEARS 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 APIGatewayWebSocketsTests: XCTestCase { + static let exampleConnectEventBody = """ + { + "headers": { + "Host": "lqrlmblaa2.execute-api.us-east-1.amazonaws.com", + "Origin": "wss://lqrlmblaa2.execute-api.us-east-1.amazonaws.com", + "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits; server_max_window_bits=15", + "Sec-WebSocket-Key": "am5ubWVpbHd3bmNyYXF0ag==", + "Sec-WebSocket-Version": "13", + "X-Amzn-Trace-Id": "Root=1-64b83950-42de8e247b4c2b43091ef67c", + "X-Forwarded-For": "24.148.42.16", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Host": [ "lqrlmblaa2.execute-api.us-east-1.amazonaws.com" ], + "Origin": [ "wss://lqrlmblaa2.execute-api.us-east-1.amazonaws.com" ], + "Sec-WebSocket-Extensions": [ + "permessage-deflate; client_max_window_bits; server_max_window_bits=15" + ], + "Sec-WebSocket-Key": [ "am5ubWVpbHd3bmNyYXF0ag==" ], + "Sec-WebSocket-Version": [ "13" ], + "X-Amzn-Trace-Id": [ "Root=1-64b83950-42de8e247b4c2b43091ef67c" ], + "X-Forwarded-For": [ "24.148.42.16" ], + "X-Forwarded-Port": [ "443" ], + "X-Forwarded-Proto": [ "https" ] + }, + "requestContext": { + "routeKey": "$connect", + "eventType": "CONNECT", + "extendedRequestId": "IU3kkGyEoAMFwZQ=", + "requestTime": "19/Jul/2023:19:28:16 +0000", + "messageDirection": "IN", + "stage": "dev", + "connectedAt": 1689794896145, + "requestTimeEpoch": 1689794896162, + "identity": { "sourceIp": "24.148.42.16" }, + "requestId": "IU3kkGyEoAMFwZQ=", + "domainName": "lqrlmblaa2.execute-api.us-east-1.amazonaws.com", + "connectionId": "IU3kkeN4IAMCJwA=", + "apiId": "lqrlmblaa2" + }, + "isBase64Encoded": false + } + """ + + // MARK: - Request - + + // MARK: Decoding + + func testRequestDecodingExampleConnectRequest() { + let data = APIGatewayWebSocketsTests.exampleConnectEventBody.data(using: .utf8)! + var req: APIGatewayWebSocketRequest? + XCTAssertNoThrow(req = try JSONDecoder().decode(APIGatewayWebSocketRequest.self, from: data)) + + XCTAssertEqual(req?.context.routeKey, "$connect") + XCTAssertEqual(req?.context.connectionId, "IU3kkeN4IAMCJwA=") + XCTAssertNil(req?.body) + } +}