Skip to content

Commit d6934fe

Browse files
feat(cloudformation): request and response models for custom resources (#19)
add cloudformation request and response models for custom resources
1 parent 2a118fb commit d6934fe

File tree

2 files changed

+308
-0
lines changed

2 files changed

+308
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2017-2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
// CloudFormation custom resource.
16+
public enum CloudFormation {
17+
// Request represents the request body of AWS::CloudFormation::CustomResource.
18+
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html
19+
public struct Request<R: Decodable, O: Decodable>: Decodable {
20+
public enum RequestType: String, Decodable {
21+
case create = "Create"
22+
case update = "Update"
23+
case delete = "Delete"
24+
}
25+
26+
let requestType: RequestType
27+
let requestId: String
28+
let responseURL: String
29+
let physicalResourceId: String?
30+
let logicalResourceId: String
31+
let stackId: String
32+
let resourceProperties: R?
33+
let oldResourceProperties: O?
34+
35+
enum CodingKeys: String, CodingKey {
36+
case requestType = "RequestType"
37+
case requestId = "RequestId"
38+
case responseURL = "ResponseURL"
39+
case physicalResourceId = "PhysicalResourceId"
40+
case logicalResourceId = "LogicalResourceId"
41+
case stackId = "StackId"
42+
case resourceProperties = "ResourceProperties"
43+
case oldResourceProperties = "OldResourceProperties"
44+
}
45+
46+
public init(from decoder: Decoder) throws {
47+
let container = try decoder.container(keyedBy: CodingKeys.self)
48+
49+
self.requestType = try container.decode(RequestType.self, forKey: .requestType)
50+
self.requestId = try container.decode(String.self, forKey: .requestId)
51+
self.responseURL = try container.decode(String.self, forKey: .responseURL)
52+
self.logicalResourceId = try container.decode(String.self, forKey: .logicalResourceId)
53+
self.stackId = try container.decode(String.self, forKey: .stackId)
54+
self.physicalResourceId = try container.decodeIfPresent(String.self, forKey: .physicalResourceId)
55+
self.resourceProperties = try container.decodeIfPresent(R.self, forKey: .resourceProperties)
56+
self.oldResourceProperties = try container.decodeIfPresent(O.self, forKey: .oldResourceProperties)
57+
}
58+
}
59+
60+
// Response represents the response body of AWS::CloudFormation::CustomResource.
61+
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html
62+
public struct Response<D: Encodable>: Encodable {
63+
public enum StatusType: String, Encodable {
64+
case success = "SUCCESS"
65+
case failed = "FAILED"
66+
}
67+
68+
let status: StatusType
69+
let requestId: String
70+
let logicalResourceId: String
71+
let stackId: String
72+
let physicalResourceId: String?
73+
let reason: String?
74+
let noEcho: Bool?
75+
let data: D?
76+
77+
enum CodingKeys: String, CodingKey {
78+
case status = "Status"
79+
case requestId = "RequestId"
80+
case logicalResourceId = "LogicalResourceId"
81+
case stackId = "StackId"
82+
case physicalResourceId = "PhysicalResourceId"
83+
case reason = "Reason"
84+
case noEcho = "NoEcho"
85+
case data = "Data"
86+
}
87+
88+
public init(
89+
status: StatusType,
90+
requestId: String,
91+
logicalResourceId: String,
92+
stackId: String,
93+
physicalResourceId: String?,
94+
reason: String?,
95+
noEcho: Bool?,
96+
data: D?
97+
) {
98+
self.status = status
99+
self.requestId = requestId
100+
self.logicalResourceId = logicalResourceId
101+
self.stackId = stackId
102+
self.physicalResourceId = physicalResourceId
103+
self.reason = reason
104+
self.noEcho = noEcho
105+
self.data = data
106+
}
107+
108+
public func encode(to encoder: Encoder) throws {
109+
var container = encoder.container(keyedBy: CodingKeys.self)
110+
111+
try container.encode(self.status.rawValue, forKey: .status)
112+
try container.encode(self.requestId, forKey: .requestId)
113+
try container.encode(self.logicalResourceId, forKey: .logicalResourceId)
114+
try container.encode(self.stackId, forKey: .stackId)
115+
try container.encodeIfPresent(self.physicalResourceId, forKey: .physicalResourceId)
116+
try container.encodeIfPresent(self.reason, forKey: .reason)
117+
try container.encodeIfPresent(self.noEcho, forKey: .noEcho)
118+
try container.encodeIfPresent(self.data, forKey: .data)
119+
}
120+
}
121+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@testable import AWSLambdaEvents
16+
import XCTest
17+
18+
class CloudFormationTests: XCTestCase {
19+
struct TestResourceProperties: Codable {
20+
let property1: String
21+
let property2: String
22+
let property3: [String]
23+
let property4: String?
24+
}
25+
26+
struct EmptyTestResourceProperties: Codable {}
27+
28+
static func eventBodyRequestRequiredFields() -> String {
29+
"""
30+
{
31+
"RequestType": "Create",
32+
"RequestId": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
33+
"StackId": "arn:aws:cloudformation:us-east-1:123456789:stack/TestStack",
34+
"ResponseURL": "http://localhost:7000/response/test",
35+
"LogicalResourceId": "TestLogicalResource"
36+
}
37+
"""
38+
}
39+
40+
static func eventBodyRequestCreate() -> String {
41+
"""
42+
{
43+
"RequestType": "Create",
44+
"RequestId": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
45+
"StackId": "arn:aws:cloudformation:us-east-1:123456789:stack/TestStack",
46+
"ResponseURL": "http://localhost:7000/response/test",
47+
"LogicalResourceId": "TestLogicalResource",
48+
"PhysicalResourceId": "TestPhysicalResource",
49+
"ResourceProperties": {
50+
"property1": "value1",
51+
"property2": "",
52+
"property3": ["1", "2", "3"],
53+
"property4": null,
54+
}
55+
}
56+
"""
57+
}
58+
59+
static func eventBodyRequestUpdate() -> String {
60+
"""
61+
{
62+
"RequestType": "Update",
63+
"RequestId": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
64+
"StackId": "arn:aws:cloudformation:us-east-1:123456789:stack/TestStack",
65+
"ResponseURL": "http://localhost:7000/response/test",
66+
"LogicalResourceId": "TestLogicalResource",
67+
"PhysicalResourceId": "TestPhysicalResource",
68+
"ResourceProperties": {
69+
"property1": "value1",
70+
"property2": "value2",
71+
"property3": ["1", "2", "3"],
72+
"property4": "value4",
73+
},
74+
"OldResourceProperties": {
75+
"property1": "value1",
76+
"property2": "",
77+
"property3": ["1", "2", "3"],
78+
"property4": null,
79+
}
80+
}
81+
"""
82+
}
83+
84+
static func eventBodyResponse() -> String {
85+
"{\"Data\":{\"property1\":\"value1\",\"property2\":\"\",\"property3\":[\"1\",\"2\",\"3\"]},\"LogicalResourceId\":\"TestLogicalResource\",\"NoEcho\":false,\"PhysicalResourceId\":\"TestPhysicalResource\",\"Reason\":\"See the details in CloudWatch Log Stream\",\"RequestId\":\"cdc73f9d-aea9-11e3-9d5a-835b769c0d9c\",\"StackId\":\"arn:aws:cloudformation:us-east-1:123456789:stack\\/TestStack\",\"Status\":\"SUCCESS\"}"
86+
}
87+
88+
func testDecodeRequestRequiredFieldsFromJSON() {
89+
let eventBody = CloudFormationTests.eventBodyRequestRequiredFields()
90+
let data = eventBody.data(using: .utf8)!
91+
var event: CloudFormation.Request<EmptyTestResourceProperties, EmptyTestResourceProperties>?
92+
XCTAssertNoThrow(event = try JSONDecoder().decode(CloudFormation.Request.self, from: data))
93+
94+
guard let event = event else {
95+
return XCTFail("Expected to have an event")
96+
}
97+
98+
XCTAssertEqual(event.requestId, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c")
99+
XCTAssertEqual(event.requestType, CloudFormation.Request.RequestType.create)
100+
XCTAssertEqual(event.stackId, "arn:aws:cloudformation:us-east-1:123456789:stack/TestStack")
101+
XCTAssertEqual(event.responseURL, "http://localhost:7000/response/test")
102+
XCTAssertEqual(event.logicalResourceId, "TestLogicalResource")
103+
XCTAssertNil(event.physicalResourceId)
104+
XCTAssertNil(event.resourceProperties)
105+
XCTAssertNil(event.oldResourceProperties)
106+
}
107+
108+
func testDecodeRequestCreateFromJSON() {
109+
let eventBody = CloudFormationTests.eventBodyRequestCreate()
110+
let data = eventBody.data(using: .utf8)!
111+
var event: CloudFormation.Request<TestResourceProperties, EmptyTestResourceProperties>?
112+
XCTAssertNoThrow(event = try JSONDecoder().decode(CloudFormation.Request.self, from: data))
113+
114+
guard let event = event else {
115+
return XCTFail("Expected to have an event")
116+
}
117+
118+
XCTAssertEqual(event.requestId, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c")
119+
XCTAssertEqual(event.requestType, CloudFormation.Request.RequestType.create)
120+
XCTAssertEqual(event.stackId, "arn:aws:cloudformation:us-east-1:123456789:stack/TestStack")
121+
XCTAssertEqual(event.responseURL, "http://localhost:7000/response/test")
122+
XCTAssertEqual(event.logicalResourceId, "TestLogicalResource")
123+
XCTAssertEqual(event.physicalResourceId, "TestPhysicalResource")
124+
XCTAssertEqual(event.resourceProperties?.property1, "value1")
125+
XCTAssertEqual(event.resourceProperties?.property2, "")
126+
XCTAssertEqual(event.resourceProperties?.property3, ["1", "2", "3"])
127+
XCTAssertEqual(event.resourceProperties?.property4, nil)
128+
XCTAssertNil(event.oldResourceProperties)
129+
}
130+
131+
func testDecodeRequestUpdateFromJSON() {
132+
let eventBody = CloudFormationTests.eventBodyRequestUpdate()
133+
let data = eventBody.data(using: .utf8)!
134+
var event: CloudFormation.Request<TestResourceProperties, TestResourceProperties>?
135+
XCTAssertNoThrow(event = try JSONDecoder().decode(CloudFormation.Request.self, from: data))
136+
137+
guard let event = event else {
138+
return XCTFail("Expected to have an event")
139+
}
140+
141+
XCTAssertEqual(event.requestId, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c")
142+
XCTAssertEqual(event.requestType, CloudFormation.Request.RequestType.update)
143+
XCTAssertEqual(event.stackId, "arn:aws:cloudformation:us-east-1:123456789:stack/TestStack")
144+
XCTAssertEqual(event.responseURL, "http://localhost:7000/response/test")
145+
XCTAssertEqual(event.logicalResourceId, "TestLogicalResource")
146+
XCTAssertEqual(event.physicalResourceId, "TestPhysicalResource")
147+
XCTAssertEqual(event.resourceProperties?.property1, "value1")
148+
XCTAssertEqual(event.resourceProperties?.property2, "value2")
149+
XCTAssertEqual(event.resourceProperties?.property3, ["1", "2", "3"])
150+
XCTAssertEqual(event.resourceProperties?.property4, "value4")
151+
XCTAssertEqual(event.oldResourceProperties?.property1, "value1")
152+
XCTAssertEqual(event.oldResourceProperties?.property2, "")
153+
XCTAssertEqual(event.oldResourceProperties?.property3, ["1", "2", "3"])
154+
XCTAssertEqual(event.oldResourceProperties?.property4, nil)
155+
}
156+
157+
func testEncodeResponseToJSON() {
158+
let resp = CloudFormation.Response<TestResourceProperties>(
159+
status: CloudFormation.Response.StatusType.success,
160+
requestId: "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
161+
logicalResourceId: "TestLogicalResource",
162+
stackId: "arn:aws:cloudformation:us-east-1:123456789:stack/TestStack",
163+
physicalResourceId: "TestPhysicalResource",
164+
reason: "See the details in CloudWatch Log Stream",
165+
noEcho: false,
166+
data: TestResourceProperties(
167+
property1: "value1",
168+
property2: "",
169+
property3: ["1", "2", "3"],
170+
property4: nil
171+
)
172+
)
173+
174+
let encoder = JSONEncoder()
175+
encoder.outputFormatting = [.sortedKeys]
176+
177+
var data: Data?
178+
XCTAssertNoThrow(data = try encoder.encode(resp))
179+
180+
var stringData: String?
181+
XCTAssertNoThrow(stringData = String(data: try XCTUnwrap(data), encoding: .utf8))
182+
183+
print(stringData ?? "")
184+
185+
XCTAssertEqual(CloudFormationTests.eventBodyResponse(), stringData)
186+
}
187+
}

0 commit comments

Comments
 (0)