diff --git a/Sources/AWSLambdaEvents/APIGateway+V2.swift b/Sources/AWSLambdaEvents/APIGateway+V2.swift index 12c4c2ce..b4a5bed2 100644 --- a/Sources/AWSLambdaEvents/APIGateway+V2.swift +++ b/Sources/AWSLambdaEvents/APIGateway+V2.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import Foundation + extension APIGateway { public struct V2 {} } @@ -117,3 +119,80 @@ extension APIGateway.V2 { } } } + +// MARK: - Codable Request body + +extension APIGateway.V2.Request { + /// Generic decoder for JSON body + /// + /// Example: + /// ``` + /// struct Body: Codable { + /// let value: String + /// } + /// + /// func handle(context: Context, event: APIGateway.V2.Request, callback: @escaping (Result) -> Void) { + /// do { + /// let body: Body? = try event.decodedBody() + /// // Do something with `body` + /// callback(.success(APIGateway.V2.Response(statusCode: .ok, body: ""))) + /// } + /// catch { + /// callback(.failure(error)) + /// } + /// } + /// ``` + /// + /// - Throws: `DecodingError` if body contains a value that couldn't be decoded + /// - Returns: Decoded body. Returns `nil` if body property is `nil`. + public func decodedBody(decoder: JSONDecoder = JSONDecoder()) throws -> Body? { + guard let bodyString = body else { + return nil + } + let data = Data(bodyString.utf8) + return try decoder.decode(Body.self, from: data) + } +} + +// MARK: - Codable Response body + +extension APIGateway.V2.Response { + /// Codable initializer for Response body + /// + /// Example: + /// ``` + /// struct Response: Codable { + /// let message: String + /// } + /// + /// func handle(context: Context, event: APIGateway.V2.Request, callback: @escaping (Result) -> Void) { + /// ... + /// callback(.success(APIGateway.V2.Response(statusCode: .ok, body: Response(message: "Hello, World!"))) + /// } + /// ``` + /// + /// - Parameters: + /// - statusCode: Response HTTP status code + /// - headers: Response HTTP headers + /// - multiValueHeaders: Resposne multi-value headers + /// - body: `Codable` response body + /// - cookies: Response cookies + /// - Throws: `EncodingError` if body could not be encoded into a JSON string + public init( + statusCode: HTTPResponseStatus, + headers: HTTPHeaders? = nil, + multiValueHeaders: HTTPMultiValueHeaders? = nil, + body: Body? = nil, + cookies: [String]? = nil, + encoder: JSONEncoder = JSONEncoder() + ) throws { + let data = try encoder.encode(body) + let bodyString = String(data: data, encoding: .utf8) + self.init(statusCode: statusCode, + headers: headers, + multiValueHeaders: multiValueHeaders, + body: bodyString, + isBase64Encoded: false, + cookies: cookies) + } +} diff --git a/Tests/AWSLambdaEventsTests/APIGateway+V2Tests.swift b/Tests/AWSLambdaEventsTests/APIGateway+V2Tests.swift index 9d682c94..fd7ddc3e 100644 --- a/Tests/AWSLambdaEventsTests/APIGateway+V2Tests.swift +++ b/Tests/AWSLambdaEventsTests/APIGateway+V2Tests.swift @@ -68,10 +68,15 @@ class APIGatewayV2Tests: XCTestCase { "x-amzn-trace-id":"Root=1-5ea3263d-07c5d5ddfd0788bed7dad831", "user-agent":"Paw/3.1.10 (Macintosh; OS X/10.15.4) GCDHTTPRequest", "content-length":"0" - } + }, + "body": "{\\"some\\":\\"json\\",\\"number\\":42}" } """ + static let exampleResponseBody = """ + {\"code\":42,\"message\":\"Foo Bar\"} + """ + // MARK: - Request - // MARK: Decoding @@ -86,6 +91,34 @@ class APIGatewayV2Tests: XCTestCase { XCTAssertEqual(req?.queryStringParameters?.count, 1) XCTAssertEqual(req?.rawQueryString, "foo=bar") XCTAssertEqual(req?.headers.count, 8) - XCTAssertNil(req?.body) + XCTAssertNotNil(req?.body) } + + func testRequestBodyDecoding() throws { + struct Body: Codable { + let some: String + let number: Int + } + + let data = APIGatewayV2Tests.exampleGetEventBody.data(using: .utf8)! + let request = try JSONDecoder().decode(APIGateway.V2.Request.self, from: data) + + let body: Body? = try request.decodedBody() + XCTAssertEqual(body?.some, "json") + XCTAssertEqual(body?.number, 42) + } + + func testResponseBodyEncoding() throws { + struct Body: Codable { + let code: Int + let message: String + } + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let body = Body(code: 42, message: "Foo Bar") + let response = try APIGateway.V2.Response(statusCode: .ok, body: body, encoder: encoder) + XCTAssertEqual(response.body, APIGatewayV2Tests.exampleResponseBody) + } + }