Skip to content

Commit 83de16a

Browse files
authored
chore: update smithy version to 1.22.0 (#425)
## Motivation awslabs/aws-sdk-swift#597 is failing due to unresolved shape smithy.api#Unit which was introduced in smithy-lang/smithy#980 ## Changes - Updated smithyVersion to 1.22.0. - Update smithyGradleVersion to 0.6.0 - Fix issues with header list parsing. The parsing was always correct but the test setup was not, where expected result always assumed that the list is a date list. To support this, I took kotlin implementation of string list parsing and translated to Swift with test cases. - JsonName trait now has no effect in AwsJson1_0 and AwsJson1_1, this change is covered at chore: update smithy version to 1.22.0 aws-sdk-swift#600 - serde change: in case where the top container is sparse map which contains a dense list as value, the dense list was considered as sparse. Therefore, added a way to track the member parent to fix the nullability. - Sensitive trait fixes because now it can't target member directly. ## Result awslabs/aws-sdk-swift#600
1 parent 44e4a16 commit 83de16a

File tree

17 files changed

+289
-100
lines changed

17 files changed

+289
-100
lines changed

Packages/ClientRuntime/Sources/Networking/Http/UnknownHttpServiceError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
/// General Service Error structure used when exact error could not be deduced from the `HttpResponse`
7-
public struct UnknownHttpServiceError: HttpServiceError {
7+
public struct UnknownHttpServiceError: HttpServiceError, Swift.Equatable {
88
public var _isThrottling: Bool = false
99

1010
public var _statusCode: HttpStatusCode?

Packages/ClientRuntime/Sources/Serialization/SerializationUtils/HeaderUtils.swift

Lines changed: 132 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,153 @@
55

66
import Foundation
77

8+
fileprivate extension String {
9+
func readNextQuoted(startIdx: String.Index, delim: Character = ",") throws -> (String.Index, String) {
10+
// startIdx is start of the quoted value, there must be at least an ending quotation mark
11+
if !(self.index(after: startIdx) < self.endIndex) {
12+
throw HeaderDeserializationError.invalidStringHeaderList(value: self)
13+
}
14+
15+
// find first non-escaped quote or end of string
16+
var endIdx = self.index(after: startIdx)
17+
while endIdx < self.endIndex {
18+
let char = self[endIdx]
19+
if char == "\\" {
20+
// skip escaped chars
21+
endIdx = self.index(after: endIdx)
22+
} else if char == "\"" {
23+
break
24+
}
25+
endIdx = self.index(after: endIdx)
26+
}
27+
28+
let next = self[self.index(after: startIdx)..<endIdx]
29+
30+
// consume trailing quote
31+
if endIdx >= self.endIndex || self[endIdx] != "\"" {
32+
throw HeaderDeserializationError.invalidStringHeaderList(value: self)
33+
}
34+
assert(endIdx < self.endIndex)
35+
assert(self[endIdx] == "\"")
36+
37+
endIdx = self.index(after: endIdx)
38+
39+
// consume delim
40+
while endIdx < self.endIndex {
41+
let char = self[endIdx]
42+
if char == " " || char == "\t" {
43+
endIdx = self.index(after: endIdx)
44+
} else if char == delim {
45+
endIdx = self.index(after: endIdx)
46+
break
47+
} else {
48+
throw HeaderDeserializationError.invalidStringHeaderList(value: self)
49+
}
50+
}
51+
52+
let unescaped = next.replacingOccurrences(of: "\\\"", with: "\"")
53+
.replacingOccurrences(of: "\\\\", with: "\\")
54+
55+
return (endIdx, unescaped)
56+
}
57+
58+
func readNextUnquoted(startIdx: String.Index, delim: Character = ",") -> (String.Index, String) {
59+
assert(startIdx < self.endIndex)
60+
61+
var endIdx = startIdx
62+
63+
while endIdx < self.endIndex && self[endIdx] != delim {
64+
endIdx = self.index(after: endIdx)
65+
}
66+
67+
let next = self[startIdx..<endIdx]
68+
if endIdx < self.endIndex && self[endIdx] == delim {
69+
endIdx = self.index(after: endIdx)
70+
}
71+
72+
return (endIdx, next.trim())
73+
}
74+
}
75+
76+
// chars in an HTTP header value that require quotations
77+
private let QUOTABLE_HEADER_VALUE_CHARS = "\",()"
78+
79+
public func quoteHeaderValue(_ value: String) -> String {
80+
if value.trim().count != value.count || value.contains(where: { char1 in
81+
QUOTABLE_HEADER_VALUE_CHARS.contains { char2 in
82+
char1 == char2
83+
}
84+
}) {
85+
let formatted = value.replacingOccurrences(of: "\\", with: "\\\\")
86+
.replacingOccurrences(of: "\"", with: "\\\"")
87+
return "\"\(formatted)\""
88+
} else {
89+
return value
90+
}
91+
}
92+
893
/// Expands the compact Header Representation of List of any type except Dates
9-
public func splitHeaderListValues(_ value: String?) -> [String]? {
10-
guard let value = value else { return nil}
11-
return value.components(separatedBy: ",").map { $0.trim() }
94+
public func splitHeaderListValues(_ value: String?) throws -> [String]? {
95+
guard let value = value else {
96+
return nil
97+
}
98+
var results: [String] = []
99+
var currIdx = value.startIndex
100+
101+
while currIdx < value.endIndex {
102+
let next: (idx: String.Index, str: String)
103+
104+
switch value[currIdx] {
105+
case " ", "\t":
106+
currIdx = value.index(after: currIdx)
107+
continue
108+
case "\"":
109+
next = try value.readNextQuoted(startIdx: currIdx)
110+
default:
111+
next = value.readNextUnquoted(startIdx: currIdx)
112+
}
113+
114+
currIdx = next.idx
115+
results.append(next.str)
116+
}
117+
118+
return results
119+
12120
}
13121

14122
/// Expands the compact HTTP Header Representation of List of Dates
15123
public func splitHttpDateHeaderListValues(_ value: String?) throws -> [String]? {
16124
guard let value = value else { return nil}
17125

18-
let separator = ","
19-
let totalSeparators = value.components(separatedBy: separator).count - 1
20-
if totalSeparators <= 1 {
126+
let n = value.filter({$0 == ","}).count
127+
128+
if n <= 1 {
21129
return [value]
22-
} else if totalSeparators % 2 == 0 {
23-
return splitHeaderListValues(value)
130+
} else if n % 2 == 0 {
131+
throw HeaderDeserializationError.invalidTimestampHeaderList(value: value)
24132
}
25133

26134
var cnt = 0
27135
var splits: [String] = []
28-
var start = 0
29-
var startIdx = value.index(value.startIndex, offsetBy: start)
30-
31-
for i in 1...value.count {
32-
let currIdx = value.index(value.startIndex, offsetBy: i-1)
33-
if value[currIdx] == "," {
136+
var startIdx = value.startIndex
137+
138+
for i in value.indices[value.startIndex..<value.endIndex] {
139+
if value[i] == "," {
34140
cnt += 1
35141
}
36-
142+
37143
// split on every other ','
38144
if cnt > 1 {
39-
startIdx = value.index(value.startIndex, offsetBy: start)
40-
splits.append(String(value[startIdx..<currIdx]).trim())
41-
start = i + 1
145+
splits.append(value[startIdx..<i].trim())
146+
startIdx = value.index(after: i)
42147
cnt = 0
43148
}
44149
}
45-
46-
if start < value.count {
47-
startIdx = value.index(value.startIndex, offsetBy: start)
48-
splits.append(String(value[startIdx..<value.endIndex]).trim())
150+
151+
if startIdx < value.endIndex {
152+
splits.append(value[startIdx...].trim())
49153
}
50-
154+
51155
return splits
52156
}
53157

@@ -64,19 +168,19 @@ extension HeaderDeserializationError: LocalizedError {
64168
switch self {
65169
case .invalidTimestampHeaderList(let value):
66170
return NSLocalizedString("Invalid HTTP Header List with Timestamps: \(value)",
67-
comment: "Client Deserialization Error")
171+
comment: "Client Deserialization Error")
68172
case .invalidTimestampHeader(let value):
69173
return NSLocalizedString("Invalid HTTP Header with Timestamp: \(value)",
70-
comment: "Client Deserialization Error")
174+
comment: "Client Deserialization Error")
71175
case .invalidBooleanHeaderList(let value):
72176
return NSLocalizedString("Invalid HTTP Header List with Booleans: \(value)",
73-
comment: "Client Deserialization Error")
177+
comment: "Client Deserialization Error")
74178
case .invalidNumbersHeaderList(let value):
75179
return NSLocalizedString("Invalid HTTP Header List with Booleans: \(value)",
76-
comment: "Client Deserialization Error")
180+
comment: "Client Deserialization Error")
77181
case .invalidStringHeaderList(let value):
78182
return NSLocalizedString("Invalid HTTP Header List with Strings: \(value)",
79-
comment: "Client Deserialization Error")
183+
comment: "Client Deserialization Error")
80184
}
81185
}
82186
}

Packages/ClientRuntime/Tests/SerializationTests/SerializationUtilsTests/HeaderUtilsTests.swift

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import ClientRuntime
99
class HeaderUtilsTests: XCTestCase {
1010

1111
func testSplitHeaderListValues() {
12-
guard let headerCollectionValues = splitHeaderListValues("1") else {
12+
guard let headerCollectionValues = try! splitHeaderListValues("1") else {
1313
XCTFail("splitting header list values unexpectedly returned nil")
1414
return
1515
}
1616
XCTAssertEqual([1], headerCollectionValues.map { Int($0) })
17-
XCTAssertEqual([1, 2, 3], splitHeaderListValues("1,2,3")?.map { Int($0) })
17+
XCTAssertEqual([1, 2, 3], try! splitHeaderListValues("1,2,3")?.map { Int($0) })
1818
// Trim whitespaces in beginning and end of string components
19-
XCTAssertEqual([1, 2, 3], splitHeaderListValues(" 1, 2, 3 ")?.map { Int($0) })
20-
XCTAssertEqual([nil, 1], splitHeaderListValues(",1")?.map { Int($0) })
19+
XCTAssertEqual([1, 2, 3], try! splitHeaderListValues(" 1, 2, 3 ")?.map { Int($0) })
20+
XCTAssertEqual([nil, 1], try! splitHeaderListValues(",1")?.map { Int($0) })
2121
}
2222

2323
func testSplitHttpDateHeaderListValues() {
@@ -31,7 +31,67 @@ class HeaderUtilsTests: XCTestCase {
3131
]
3232

3333
for (headerListString, headerList) in dateHeaderTransformations {
34-
XCTAssertEqual(headerList, try? splitHttpDateHeaderListValues(headerListString))
34+
XCTAssertEqual(headerList, try! splitHttpDateHeaderListValues(headerListString))
35+
}
36+
37+
XCTAssertThrowsError(try splitHttpDateHeaderListValues("Mon, 16 Dec 2019 23:48:18 GMT, , Tue, 17 Dec 2019 23:48:18 GMT")) { error in
38+
XCTAssertEqual("Invalid HTTP Header List with Timestamps: Mon, 16 Dec 2019 23:48:18 GMT, , Tue, 17 Dec 2019 23:48:18 GMT", error.localizedDescription)
39+
}
40+
}
41+
42+
func testSplitBoolList() {
43+
XCTAssertEqual(["true", "false", "true", "true"], try! splitHeaderListValues("true,\"false\",true,\"true\""))
44+
}
45+
46+
func testSplitIntList() {
47+
XCTAssertEqual(["1"], try! splitHeaderListValues("1"))
48+
XCTAssertEqual(["1", "2", "3"], try! splitHeaderListValues("1,2,3"))
49+
XCTAssertEqual(["1", "2", "3"], try! splitHeaderListValues("1, 2, 3"))
50+
51+
// quoted
52+
XCTAssertEqual(["1", "2", "3", "-4", "5"], try! splitHeaderListValues("1,\"2\",3,\"-4\",5"))
53+
}
54+
55+
func testSplitStringList() {
56+
XCTAssertEqual(["foo"], try! splitHeaderListValues("foo"))
57+
58+
// trailing space
59+
XCTAssertEqual(["fooTrailing"], try! splitHeaderListValues("fooTrailing "))
60+
61+
// leading and trailing space
62+
XCTAssertEqual([" foo "], try! splitHeaderListValues("\" foo \""))
63+
64+
// ignore spaces between values
65+
XCTAssertEqual(["foo", "bar"], try! splitHeaderListValues("foo , bar"))
66+
XCTAssertEqual(["foo", "bar"], try! splitHeaderListValues("\"foo\" , \"bar\""))
67+
68+
// comma in quotes
69+
XCTAssertEqual(["foo,bar", "baz"], try! splitHeaderListValues("\"foo,bar\",baz"))
70+
71+
// comm in quotes w/trailing space
72+
XCTAssertEqual(["foo,bar", "baz"], try! splitHeaderListValues("\"foo,bar\",baz "))
73+
74+
// quote in quotes
75+
XCTAssertEqual(["foo\",bar", "\"asdf\"", "baz"], try! splitHeaderListValues("\"foo\\\",bar\",\"\\\"asdf\\\"\",baz"))
76+
77+
// quote in quote w/spaces
78+
XCTAssertEqual(["foo\",bar", "\"asdf \"", "baz"], try! splitHeaderListValues("\"foo\\\",bar\", \"\\\"asdf \\\"\", baz"))
79+
80+
// empty quotes
81+
XCTAssertEqual(["", "baz"], try! splitHeaderListValues("\"\",baz"))
82+
83+
// escaped slashes
84+
XCTAssertEqual(["foo", "(foo\\bar)"], try! splitHeaderListValues("foo, \"(foo\\\\bar)\""))
85+
86+
// empty
87+
XCTAssertEqual(["", "1"], try! splitHeaderListValues(",1"))
88+
89+
XCTAssertThrowsError(try splitHeaderListValues("foo, bar, \"baz")) { error in
90+
XCTAssertEqual("Invalid HTTP Header List with Strings: foo, bar, \"baz", error.localizedDescription)
91+
}
92+
93+
XCTAssertThrowsError(try splitHeaderListValues("foo , \"bar\" \tf,baz")) { error in
94+
XCTAssertEqual("Invalid HTTP Header List with Strings: foo , \"bar\" \tf,baz", error.localizedDescription)
3595
}
3696
}
3797
}

Packages/SmithyTestUtil/Sources/RequestTestUtil/HttpRequestTestBase.swift

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,7 @@ open class HttpRequestTestBase: XCTestCase {
5757
if let headers = headers {
5858
for (headerName, headerValue) in headers {
5959
let value = sanitizeStringForNonConformingValues(headerValue)
60-
do {
61-
if let values = try splitHttpDateHeaderListValues(value) {
62-
builder.withHeader(name: headerName, values: values)
63-
}
64-
} catch let err {
65-
XCTFail(err.localizedDescription)
66-
}
60+
builder.withHeader(name: headerName, value: value)
6761
}
6862
}
6963

@@ -288,20 +282,20 @@ open class HttpRequestTestBase: XCTestCase {
288282
XCTFail("There are expected headers and no actual headers.")
289283
return
290284
}
291-
292-
for expectedHeader in expected.headers {
293-
XCTAssertTrue(actual.exists(name: expectedHeader.name),
294-
"expected header \(expectedHeader.name) has no actual values")
295-
guard let values = actual.values(for: expectedHeader.name) else {
296-
XCTFail("actual values expected to not be null")
285+
286+
expected.headers.forEach { header in
287+
XCTAssertTrue(actual.exists(name: header.name))
288+
289+
guard actual.values(for: header.name) != header.value else {
290+
XCTAssertEqual(actual.values(for: header.name), header.value)
297291
return
298292
}
299293

300-
XCTAssert(expectedHeader.value.containsSameElements(as: values),
301-
"""
302-
expected header name value pair not equal: \(expectedHeader.name):
303-
\(expectedHeader.value); found: \(expectedHeader.name):\(values)
304-
""")
294+
let actualValue = actual.values(for: header.name)?.joined(separator: ", ")
295+
XCTAssertNotNil(actualValue)
296+
297+
let expectedValue = header.value.joined(separator: ", ")
298+
XCTAssertEqual(actualValue, expectedValue)
305299
}
306300
}
307301

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ kotlin.code.style=official
33
# config
44

55
# codegen
6-
smithyVersion=1.13.1
7-
smithyGradleVersion=0.5.3
6+
smithyVersion=1.22.0
7+
smithyGradleVersion=0.6.0
88

99
# kotlin
1010
kotlinVersion=1.5.31

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/CodegenVisitor.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ class CodegenVisitor(context: PluginContext) : ShapeVisitor.Default<Void>() {
155155

156156
override fun structureShape(shape: StructureShape): Void? {
157157
writers.useShapeWriter(shape) { writer: SwiftWriter -> StructureGenerator(model, symbolProvider, writer, shape, settings, protocolGenerator?.serviceErrorProtocolSymbol).render() }
158-
if (shape.hasTrait<SensitiveTrait>() || shape.members().any { it.hasTrait<SensitiveTrait>() }) {
158+
if (shape.hasTrait<SensitiveTrait>() || shape.members().any { it.hasTrait<SensitiveTrait>() || model.expectShape(it.target).hasTrait<SensitiveTrait>() }) {
159159
writers.useShapeExtensionWriter(shape, "CustomDebugStringConvertible") { writer: SwiftWriter ->
160-
CustomDebugStringConvertibleGenerator(symbolProvider, writer, shape).render()
160+
CustomDebugStringConvertibleGenerator(symbolProvider, writer, shape, model).render()
161161
}
162162
}
163163
return null

0 commit comments

Comments
 (0)