Skip to content

Commit c1f3dea

Browse files
authored
chore: Make modeled errors Sendable (#883)
1 parent fad1c76 commit c1f3dea

File tree

5 files changed

+95
-73
lines changed

5 files changed

+95
-73
lines changed

Sources/SmithyHTTPAPI/HTTPResponse.swift

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,48 @@
88
import protocol Smithy.ResponseMessage
99
import protocol Smithy.Stream
1010
import enum Smithy.ByteStream
11-
import class Foundation.DispatchQueue
11+
import class Foundation.NSRecursiveLock
1212

13-
public class HTTPResponse: HTTPURLResponse, ResponseMessage {
13+
public final class HTTPResponse: ResponseMessage, @unchecked Sendable {
14+
private var lock = NSRecursiveLock()
1415

15-
public var headers: Headers
16-
public var body: ByteStream
17-
public var reason: String?
16+
private var _headers: Headers
17+
public var headers: Headers {
18+
get { lock.lock(); defer { lock.unlock() }; return _headers }
19+
set { lock.lock(); defer { lock.unlock() }; self._headers = newValue }
20+
}
21+
22+
private var _body: ByteStream
23+
public var body: ByteStream {
24+
get { lock.lock(); defer { lock.unlock() }; return _body }
25+
set { lock.lock(); defer { lock.unlock() }; self._body = newValue }
26+
}
1827

1928
private var _statusCode: HTTPStatusCode
20-
private let statusCodeQueue = DispatchQueue(label: "statusCodeSerialQueue")
2129
public var statusCode: HTTPStatusCode {
22-
get {
23-
statusCodeQueue.sync {
24-
return _statusCode
25-
}
26-
}
27-
set {
28-
statusCodeQueue.sync {
29-
self._statusCode = newValue
30-
}
31-
}
30+
get { lock.lock(); defer { lock.unlock() }; return _statusCode }
31+
set { lock.lock(); defer { lock.unlock() }; self._statusCode = newValue }
3232
}
3333

34+
public let reason: String?
35+
3436
public init(
3537
headers: Headers = .init(),
3638
statusCode: HTTPStatusCode = .processing,
3739
body: ByteStream = .noStream,
38-
reason: String? = nil) {
39-
self.headers = headers
40+
reason: String? = nil
41+
) {
42+
self._headers = headers
4043
self._statusCode = statusCode
41-
self.body = body
44+
self._body = body
45+
self.reason = reason
4246
}
4347

4448
public init(headers: Headers = .init(), body: ByteStream, statusCode: HTTPStatusCode, reason: String? = nil) {
45-
self.body = body
49+
self._body = body
4650
self._statusCode = statusCode
47-
self.headers = headers
51+
self._headers = headers
52+
self.reason = reason
4853
}
4954

5055
/**

Sources/SmithyHTTPAPI/HTTPURLResponse.swift

Lines changed: 0 additions & 15 deletions
This file was deleted.

Sources/SmithyHTTPAPI/Headers.swift

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,28 @@
55
// SPDX-License-Identifier: Apache-2.0
66
//
77

8-
public struct Headers: Sendable {
9-
public var headers: [Header] = []
8+
import class Foundation.NSRecursiveLock
9+
10+
public struct Headers: @unchecked Sendable {
11+
private let lock = NSRecursiveLock()
12+
13+
private var _headers: [Header] = []
14+
public var headers: [Header] {
15+
get { access { $0 } }
16+
set { mutate { $0 = newValue } }
17+
}
18+
19+
private mutating func mutate(_ block: (inout [Header]) -> Void) {
20+
lock.lock()
21+
defer { lock.unlock() }
22+
block(&_headers)
23+
}
24+
25+
private func access<T>(_ block: ([Header]) throws -> T) rethrows -> T {
26+
lock.lock()
27+
defer { lock.unlock() }
28+
return try block(_headers)
29+
}
1030

1131
/// Creates an empty instance.
1232
public init() {}
@@ -53,11 +73,13 @@ public struct Headers: Sendable {
5373
/// - Parameters:
5474
/// - header: The `Header` to be added or updated.
5575
public mutating func add(_ header: Header) {
56-
guard let index = headers.index(of: header.name) else {
57-
headers.append(header)
58-
return
76+
mutate { headers in
77+
guard let index = headers.index(of: header.name) else {
78+
headers.append(header)
79+
return
80+
}
81+
headers[index].value.append(contentsOf: header.value)
5982
}
60-
headers[index].value.append(contentsOf: header.value)
6183
}
6284

6385
/// Case-insensitively updates the value of a `Header` by replacing the values of it or appends a `Header`
@@ -66,11 +88,13 @@ public struct Headers: Sendable {
6688
/// - Parameters:
6789
/// - header: The `Header` to be added or updated.
6890
public mutating func update(_ header: Header) {
69-
guard let index = headers.index(of: header.name) else {
70-
headers.append(header)
71-
return
91+
mutate { headers in
92+
guard let index = headers.index(of: header.name) else {
93+
headers.append(header)
94+
return
95+
}
96+
headers.replaceSubrange(index...index, with: [header])
7297
}
73-
headers.replaceSubrange(index...index, with: [header])
7498
}
7599

76100
/// Case-insensitively updates the value of a `Header` by replacing the values of it or appends a `Header`
@@ -97,17 +121,20 @@ public struct Headers: Sendable {
97121
///
98122
/// - Parameters:
99123
/// - headers: The `Headers` object.
100-
public mutating func addAll(headers: Headers) {
101-
self.headers.append(contentsOf: headers.headers)
124+
public mutating func addAll(headers otherHeaders: Headers) {
125+
mutate { headers in
126+
headers.append(contentsOf: otherHeaders.headers)
127+
}
102128
}
103129

104130
/// Case-insensitively removes a `Header`, if it exists, from the instance.
105131
///
106132
/// - Parameter name: The name of the `HTTPHeader` to remove.
107133
public mutating func remove(name: String) {
108-
guard let index = headers.index(of: name) else { return }
109-
110-
headers.remove(at: index)
134+
mutate { headers in
135+
guard let index = headers.index(of: name) else { return }
136+
headers.remove(at: index)
137+
}
111138
}
112139

113140
/// Case-insensitively find a header's values by name.
@@ -116,13 +143,14 @@ public struct Headers: Sendable {
116143
///
117144
/// - Returns: The values of the header, if they exist.
118145
public func values(for name: String) -> [String]? {
119-
guard let indices = headers.indices(of: name), !indices.isEmpty else { return nil }
120-
var values = [String]()
121-
for index in indices {
122-
values.append(contentsOf: headers[index].value)
146+
access { headers in
147+
guard let indices = headers.indices(of: name), !indices.isEmpty else { return nil }
148+
var values = [String]()
149+
for index in indices {
150+
values.append(contentsOf: headers[index].value)
151+
}
152+
return values
123153
}
124-
125-
return values
126154
}
127155

128156
/// Case-insensitively find a header's value by name.
@@ -131,29 +159,28 @@ public struct Headers: Sendable {
131159
///
132160
/// - Returns: The value of header as a comma delimited string, if it exists.
133161
public func value(for name: String) -> String? {
134-
guard let values = values(for: name) else {
135-
return nil
136-
}
162+
guard let values = values(for: name) else { return nil }
137163
return values.joined(separator: ",")
138164
}
139165

140166
public func exists(name: String) -> Bool {
141-
headers.index(of: name) != nil
167+
access { $0.index(of: name) != nil }
142168
}
143169

144170
/// The dictionary representation of all headers.
145171
///
146172
/// This representation does not preserve the current order of the instance.
147173
public var dictionary: [String: [String]] {
148-
let namesAndValues = headers.map { ($0.name, $0.value) }
149-
150-
return Dictionary(namesAndValues) { (first, last) -> [String] in
151-
return first + last
174+
access { headers in
175+
let namesAndValues = headers.map { ($0.name, $0.value) }
176+
return Dictionary(namesAndValues) { (first, last) -> [String] in
177+
first + last
178+
}
152179
}
153180
}
154181

155182
public var isEmpty: Bool {
156-
return self.headers.isEmpty
183+
access { $0.isEmpty }
157184
}
158185
}
159186

@@ -164,14 +191,18 @@ extension Headers: Equatable {
164191
/// - rhs: The second `Headers` to compare.
165192
/// - Returns: `true` if the two values are equal irrespective of order, otherwise `false`.
166193
public static func == (lhs: Headers, rhs: Headers) -> Bool {
167-
return lhs.headers.sorted() == rhs.headers.sorted()
194+
lhs.access { lhsHeaders in
195+
rhs.access { rhsHeaders in
196+
lhsHeaders.sorted() == rhsHeaders.sorted()
197+
}
198+
}
168199
}
169200
}
170201

171202
extension Headers: Hashable {
172203

173204
public func hash(into hasher: inout Hasher) {
174-
hasher.combine(headers.sorted())
205+
access { hasher.combine($0.sorted()) }
175206
}
176207
}
177208

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,11 @@ class StructureGenerator(
215215

216216
writer.writeAvailableAttribute(model, shape)
217217
writer.openBlock(
218-
"public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N {",
218+
"public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N, \$N {",
219219
ClientRuntimeTypes.Core.ModeledError,
220220
ClientRuntimeTypes.Http.HttpError,
221-
SwiftTypes.Error
221+
SwiftTypes.Error,
222+
SwiftTypes.Protocols.Sendable,
222223
)
223224
.call { generateErrorStructMembers() }
224225
.write("")
@@ -234,7 +235,7 @@ class StructureGenerator(
234235
private fun generateErrorStructMembers() {
235236
if (membersSortedByName.isNotEmpty()) {
236237
writer.write("")
237-
writer.openBlock("public struct Properties {", "}") {
238+
writer.openBlock("public struct Properties: \$N {", "}", SwiftTypes.Protocols.Sendable) {
238239
membersSortedByName.forEach {
239240
val (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(it) { return@forEach }
240241
writer.writeMemberDocs(model, it)

smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructureGeneratorTests.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,9 @@ public struct RecursiveShapesInputOutputLists: Swift.Sendable {
262262

263263
contents.shouldContain(swiftSettings.copyrightNotice)
264264
val expectedGeneratedStructure = """
265-
public struct MyError: ClientRuntime.ModeledError, ClientRuntime.ServiceError, ClientRuntime.HTTPError, Swift.Error {
265+
public struct MyError: ClientRuntime.ModeledError, ClientRuntime.ServiceError, ClientRuntime.HTTPError, Swift.Error, Swift.Sendable {
266266
267-
public struct Properties {
267+
public struct Properties: Swift.Sendable {
268268
/// This is documentation about the member.
269269
public internal(set) var baz: Swift.Int? = nil
270270
public internal(set) var message: Swift.String? = nil

0 commit comments

Comments
 (0)