From d3bf67c1c02817fb9fcd489ed030c64d3c5e2601 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Tue, 28 Jan 2025 21:51:34 +0000 Subject: [PATCH 1/4] Add bodyParts computed prop to StreamingClientResponse --- .../GRPCCore/Call/Client/ClientResponse.swift | 9 ++++ .../Call/Client/ClientResponseTests.swift | 43 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Sources/GRPCCore/Call/Client/ClientResponse.swift b/Sources/GRPCCore/Call/Client/ClientResponse.swift index b99f6f3ae..36dd3d5aa 100644 --- a/Sources/GRPCCore/Call/Client/ClientResponse.swift +++ b/Sources/GRPCCore/Call/Client/ClientResponse.swift @@ -382,4 +382,13 @@ extension StreamingClientResponse { return RPCAsyncSequence.throwing(error) } } + + /// Returns the body parts (i.e. `messages` and `trailingMetadata`) returned from the server. + /// + /// For rejected RPCs (in other words, where ``accepted`` is `failure`), this method throws an `RPCError`. + public var bodyParts: RPCAsyncSequence { + get throws { + try self.accepted.get().bodyParts + } + } } diff --git a/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift b/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift index 01557d02c..725b212f0 100644 --- a/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift +++ b/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift @@ -53,7 +53,7 @@ final class ClientResponseTests: XCTestCase { XCTAssertEqual(response.trailingMetadata, ["bar": "baz"]) } - func testAcceptedStreamResponseConvenienceMethods() async throws { + func testAcceptedStreamResponseConvenienceMethods_Messages() async throws { let response = StreamingClientResponse( of: String.self, metadata: ["foo": "bar"], @@ -73,6 +73,29 @@ final class ClientResponseTests: XCTestCase { XCTAssertEqual(messages, ["foo", "bar", "baz"]) } + func testAcceptedStreamResponseConvenienceMethods_BodyParts() async throws { + let response = StreamingClientResponse( + of: String.self, + metadata: ["foo": "bar"], + bodyParts: RPCAsyncSequence( + wrapping: AsyncThrowingStream { + $0.yield(.message("foo")) + $0.yield(.message("bar")) + $0.yield(.message("baz")) + $0.yield(.trailingMetadata(["baz": "baz"])) + $0.finish() + } + ) + ) + + XCTAssertEqual(response.metadata, ["foo": "bar"]) + let bodyParts = try await response.bodyParts.collect() + XCTAssertEqual( + bodyParts, + [.message("foo"), .message("bar"), .message("baz"), .trailingMetadata(["baz": "baz"])] + ) + } + func testRejectedStreamResponseConvenienceMethods() async throws { let error = RPCError(code: .aborted, message: "error message", metadata: ["bar": "baz"]) let response = StreamingClientResponse(of: String.self, error: error) @@ -182,3 +205,21 @@ final class ClientResponseTests: XCTestCase { } } } + +extension StreamingClientResponse.Contents.BodyPart: Equatable where Message: Equatable { + static func == ( + lhs: StreamingClientResponse.Contents.BodyPart, + rhs: StreamingClientResponse.Contents.BodyPart + ) -> Bool { + switch (lhs, rhs) { + case (.message(let lhsMessage), .message(let rhsMessage)): + return lhsMessage == rhsMessage + + case (.trailingMetadata(let lhsMetadata), .trailingMetadata(let rhsMetadata)): + return lhsMetadata == rhsMetadata + + default: + return false + } + } +} From 095237687f74c3e5e050bad767440d3f8b319c2f Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Tue, 28 Jan 2025 22:19:36 +0000 Subject: [PATCH 2/4] Update ClientResponseTests.swift --- Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift b/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift index 725b212f0..cc1b3beb1 100644 --- a/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift +++ b/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift @@ -106,6 +106,11 @@ final class ClientResponseTests: XCTestCase { } errorHandler: { XCTAssertEqual($0, error) } + await XCTAssertThrowsRPCErrorAsync { + try response.bodyParts + } errorHandler: { + XCTAssertEqual($0, error) + } } func testStreamToSingleConversionForValidStream() async throws { From b11dcbe9e42e305c102f4b4ec2394e04ed1bb5fa Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Wed, 29 Jan 2025 08:50:30 +0000 Subject: [PATCH 3/4] PR changes --- .../GRPCCore/Call/Client/ClientResponse.swift | 14 +++++++++---- .../Call/Client/ClientResponseTests.swift | 20 +------------------ 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/Sources/GRPCCore/Call/Client/ClientResponse.swift b/Sources/GRPCCore/Call/Client/ClientResponse.swift index 36dd3d5aa..0577ff4b7 100644 --- a/Sources/GRPCCore/Call/Client/ClientResponse.swift +++ b/Sources/GRPCCore/Call/Client/ClientResponse.swift @@ -363,7 +363,7 @@ extension StreamingClientResponse { /// Returns the messages received from the server. /// - /// For rejected RPCs the `RPCAsyncSequence` throws a `RPCError``. + /// For rejected RPCs (in other words, where ``accepted`` is `failure`), the `RPCAsyncSequence` throws a `RPCError``. public var messages: RPCAsyncSequence { switch self.accepted { case let .success(contents): @@ -385,10 +385,16 @@ extension StreamingClientResponse { /// Returns the body parts (i.e. `messages` and `trailingMetadata`) returned from the server. /// - /// For rejected RPCs (in other words, where ``accepted`` is `failure`), this method throws an `RPCError`. + /// For rejected RPCs (in other words, where ``accepted`` is `failure`), the `RPCAsyncSequence` throws a `RPCError``. public var bodyParts: RPCAsyncSequence { - get throws { - try self.accepted.get().bodyParts + switch self.accepted { + case let .success(contents): + return contents.bodyParts + + case let .failure(error): + return RPCAsyncSequence.throwing(error) } } } + +extension StreamingClientResponse.Contents.BodyPart: Equatable where Message: Equatable {} diff --git a/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift b/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift index cc1b3beb1..46b0f19ae 100644 --- a/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift +++ b/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift @@ -107,7 +107,7 @@ final class ClientResponseTests: XCTestCase { XCTAssertEqual($0, error) } await XCTAssertThrowsRPCErrorAsync { - try response.bodyParts + try await response.bodyParts.collect() } errorHandler: { XCTAssertEqual($0, error) } @@ -210,21 +210,3 @@ final class ClientResponseTests: XCTestCase { } } } - -extension StreamingClientResponse.Contents.BodyPart: Equatable where Message: Equatable { - static func == ( - lhs: StreamingClientResponse.Contents.BodyPart, - rhs: StreamingClientResponse.Contents.BodyPart - ) -> Bool { - switch (lhs, rhs) { - case (.message(let lhsMessage), .message(let rhsMessage)): - return lhsMessage == rhsMessage - - case (.trailingMetadata(let lhsMetadata), .trailingMetadata(let rhsMetadata)): - return lhsMetadata == rhsMetadata - - default: - return false - } - } -} From 9d12f1ac670bfaf941baf140bbf1fd4405f62dbb Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Wed, 29 Jan 2025 11:52:38 +0000 Subject: [PATCH 4/4] Fix docs --- Sources/GRPCCore/Call/Client/ClientResponse.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/GRPCCore/Call/Client/ClientResponse.swift b/Sources/GRPCCore/Call/Client/ClientResponse.swift index 0577ff4b7..73fcde0d5 100644 --- a/Sources/GRPCCore/Call/Client/ClientResponse.swift +++ b/Sources/GRPCCore/Call/Client/ClientResponse.swift @@ -363,7 +363,7 @@ extension StreamingClientResponse { /// Returns the messages received from the server. /// - /// For rejected RPCs (in other words, where ``accepted`` is `failure`), the `RPCAsyncSequence` throws a `RPCError``. + /// For rejected RPCs (in other words, where ``accepted`` is `failure`), the `RPCAsyncSequence` throws a ``RPCError``. public var messages: RPCAsyncSequence { switch self.accepted { case let .success(contents): @@ -385,7 +385,7 @@ extension StreamingClientResponse { /// Returns the body parts (i.e. `messages` and `trailingMetadata`) returned from the server. /// - /// For rejected RPCs (in other words, where ``accepted`` is `failure`), the `RPCAsyncSequence` throws a `RPCError``. + /// For rejected RPCs (in other words, where ``accepted`` is `failure`), the `RPCAsyncSequence` throws a ``RPCError``. public var bodyParts: RPCAsyncSequence { switch self.accepted { case let .success(contents):