Skip to content

Commit dd22b39

Browse files
authored
Allow metadata to be mutated on server response types (grpc#2120)
Motivation: The server response types have a metadata computed property with only a getter. It's entirely possible to mutate the metadata manually, it's just a bit of a faff. This should be easier. Modifications: - Add a setter to the computed property - Migrate server response tests to swift-testing Result: - Easier to use API
1 parent e160fd0 commit dd22b39

File tree

2 files changed

+107
-54
lines changed

2 files changed

+107
-54
lines changed

Sources/GRPCCore/Call/Server/ServerResponse.swift

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,25 @@ extension ServerResponse {
244244
self.accepted = .failure(error)
245245
}
246246

247-
/// Returns the metadata to be sent to the client at the start of the response.
248-
///
249-
/// For rejected RPCs (in other words, where ``accepted`` is `failure`) the metadata is empty.
247+
/// The metadata to be sent to the client at the start of the response.
250248
public var metadata: Metadata {
251-
switch self.accepted {
252-
case let .success(contents):
253-
return contents.metadata
254-
case .failure:
255-
return [:]
249+
get {
250+
switch self.accepted {
251+
case let .success(contents):
252+
return contents.metadata
253+
case .failure(let error):
254+
return error.metadata
255+
}
256+
}
257+
set {
258+
switch self.accepted {
259+
case var .success(contents):
260+
contents.metadata = newValue
261+
self.accepted = .success(contents)
262+
case var .failure(error):
263+
error.metadata = newValue
264+
self.accepted = .failure(error)
265+
}
256266
}
257267
}
258268

@@ -303,15 +313,25 @@ extension StreamingServerResponse {
303313
self.accepted = .failure(error)
304314
}
305315

306-
/// Returns metadata received from the server at the start of the response.
307-
///
308-
/// For rejected RPCs (in other words, where ``accepted`` is `failure`) the metadata is empty.
316+
/// The metadata to be sent to the client at the start of the response.
309317
public var metadata: Metadata {
310-
switch self.accepted {
311-
case let .success(contents):
312-
return contents.metadata
313-
case .failure:
314-
return [:]
318+
get {
319+
switch self.accepted {
320+
case let .success(contents):
321+
return contents.metadata
322+
case .failure(let error):
323+
return error.metadata
324+
}
325+
}
326+
set {
327+
switch self.accepted {
328+
case var .success(contents):
329+
contents.metadata = newValue
330+
self.accepted = .success(contents)
331+
case var .failure(error):
332+
error.metadata = newValue
333+
self.accepted = .failure(error)
334+
}
315335
}
316336
}
317337
}

Tests/GRPCCoreTests/Call/Server/ServerResponseTests.swift

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,65 +13,68 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
@_spi(Testing) import GRPCCore
17-
import XCTest
1816

19-
final class ServerResponseTests: XCTestCase {
20-
func testSingleConvenienceInit() {
21-
var response = ServerResponse(
17+
import GRPCCore
18+
import Testing
19+
20+
@Suite("ServerResponse")
21+
struct ServerResponseTests {
22+
@Test("ServerResponse(message:metadata:trailingMetadata:)")
23+
func responseInitSuccess() throws {
24+
let response = ServerResponse(
2225
message: "message",
2326
metadata: ["metadata": "initial"],
2427
trailingMetadata: ["metadata": "trailing"]
2528
)
2629

27-
switch response.accepted {
28-
case .success(let contents):
29-
XCTAssertEqual(contents.message, "message")
30-
XCTAssertEqual(contents.metadata, ["metadata": "initial"])
31-
XCTAssertEqual(contents.trailingMetadata, ["metadata": "trailing"])
32-
case .failure:
33-
XCTFail("Unexpected error")
34-
}
30+
let contents = try #require(try response.accepted.get())
31+
#expect(contents.message == "message")
32+
#expect(contents.metadata == ["metadata": "initial"])
33+
#expect(contents.trailingMetadata == ["metadata": "trailing"])
34+
}
3535

36+
@Test("ServerResponse(of:error:)")
37+
func responseInitError() throws {
3638
let error = RPCError(code: .aborted, message: "Aborted")
37-
response = ServerResponse(of: String.self, error: error)
39+
let response = ServerResponse(of: String.self, error: error)
3840
switch response.accepted {
3941
case .success:
40-
XCTFail("Unexpected success")
41-
case .failure(let error):
42-
XCTAssertEqual(error, error)
42+
Issue.record("Expected error")
43+
case .failure(let rpcError):
44+
#expect(rpcError == error)
4345
}
4446
}
4547

46-
func testStreamConvenienceInit() async throws {
47-
var response = StreamingServerResponse(
48+
@Test("StreamingServerResponse(of:metadata:producer:)")
49+
func streamingResponseInitSuccess() async throws {
50+
let response = StreamingServerResponse(
4851
of: String.self,
4952
metadata: ["metadata": "initial"]
5053
) { _ in
5154
// Empty body.
5255
return ["metadata": "trailing"]
5356
}
5457

55-
switch response.accepted {
56-
case .success(let contents):
57-
XCTAssertEqual(contents.metadata, ["metadata": "initial"])
58-
let trailingMetadata = try await contents.producer(.failTestOnWrite())
59-
XCTAssertEqual(trailingMetadata, ["metadata": "trailing"])
60-
case .failure:
61-
XCTFail("Unexpected error")
62-
}
58+
let contents = try #require(try response.accepted.get())
59+
#expect(contents.metadata == ["metadata": "initial"])
60+
let trailingMetadata = try await contents.producer(.failTestOnWrite())
61+
#expect(trailingMetadata == ["metadata": "trailing"])
62+
}
6363

64+
@Test("StreamingServerResponse(of:error:)")
65+
func streamingResponseInitError() async throws {
6466
let error = RPCError(code: .aborted, message: "Aborted")
65-
response = StreamingServerResponse(of: String.self, error: error)
67+
let response = StreamingServerResponse(of: String.self, error: error)
6668
switch response.accepted {
6769
case .success:
68-
XCTFail("Unexpected success")
69-
case .failure(let error):
70-
XCTAssertEqual(error, error)
70+
Issue.record("Expected error")
71+
case .failure(let rpcError):
72+
#expect(rpcError == error)
7173
}
7274
}
7375

74-
func testSingleToStreamConversionForSuccessfulResponse() async throws {
76+
@Test("StreamingServerResponse(single:) (accepted)")
77+
func singleToStreamConversionForSuccessfulResponse() async throws {
7578
let single = ServerResponse(
7679
message: "foo",
7780
metadata: ["metadata": "initial"],
@@ -90,19 +93,49 @@ final class ServerResponseTests: XCTestCase {
9093
throw error
9194
}
9295

93-
XCTAssertEqual(stream.metadata, ["metadata": "initial"])
96+
#expect(stream.metadata == ["metadata": "initial"])
9497
let collected = try await messages.collect()
95-
XCTAssertEqual(collected, ["foo"])
96-
XCTAssertEqual(trailingMetadata, ["metadata": "trailing"])
98+
#expect(collected == ["foo"])
99+
#expect(trailingMetadata == ["metadata": "trailing"])
97100
}
98101

99-
func testSingleToStreamConversionForFailedResponse() async throws {
102+
@Test("StreamingServerResponse(single:) (rejected)")
103+
func singleToStreamConversionForFailedResponse() async throws {
100104
let error = RPCError(code: .aborted, message: "aborted")
101105
let single = ServerResponse(of: String.self, error: error)
102106
let stream = StreamingServerResponse(single: single)
103107

104-
XCTAssertThrowsRPCError(try stream.accepted.get()) {
105-
XCTAssertEqual($0, error)
108+
switch stream.accepted {
109+
case .success:
110+
Issue.record("Expected error")
111+
case .failure(let rpcError):
112+
#expect(rpcError == error)
113+
}
114+
}
115+
116+
@Test("Mutate metadata on response", arguments: [true, false])
117+
func mutateMetadataOnResponse(accepted: Bool) {
118+
var response: ServerResponse<String>
119+
if accepted {
120+
response = ServerResponse(message: "")
121+
} else {
122+
response = ServerResponse(error: RPCError(code: .aborted, message: ""))
106123
}
124+
125+
response.metadata.addString("value", forKey: "key")
126+
#expect(response.metadata == ["key": "value"])
127+
}
128+
129+
@Test("Mutate metadata on streaming response", arguments: [true, false])
130+
func mutateMetadataOnStreamingResponse(accepted: Bool) {
131+
var response: StreamingServerResponse<String>
132+
if accepted {
133+
response = StreamingServerResponse { _ in [:] }
134+
} else {
135+
response = StreamingServerResponse(error: RPCError(code: .aborted, message: ""))
136+
}
137+
138+
response.metadata.addString("value", forKey: "key")
139+
#expect(response.metadata == ["key": "value"])
107140
}
108141
}

0 commit comments

Comments
 (0)