From d5e0d7048089bc68b7f70f7ad3edc98c3f771ac1 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Mon, 7 Oct 2024 17:19:13 +0100 Subject: [PATCH] Add a merge method to Metadata (#2084) ## Motivation We want to be able to flatten `RPCError`s, and to do so we need to be able to merge the `Metadata` contained in each. ## Modifications This PR adds a helper function to merge one `Metadata` instance into another. ## Result Unblocks https://github.com/grpc/grpc-swift/pull/2083 and also provides a potentially useful API for users. **- Note:** Because of the way `Metadata` has been implemented, we can have multiple _identical_ key-value pairs. This isn't ideal, as it's particularly feasible that we'll end up with multiple repeated identical pairs when merging two `Metadata`s. I think we should reconsider the backing data structure (using a set for example) or add a check before inserting to avoid this. --- Sources/GRPCCore/Metadata.swift | 14 ++++++ Tests/GRPCCoreTests/MetadataTests.swift | 62 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/Sources/GRPCCore/Metadata.swift b/Sources/GRPCCore/Metadata.swift index 8326eb336..dfc095e1e 100644 --- a/Sources/GRPCCore/Metadata.swift +++ b/Sources/GRPCCore/Metadata.swift @@ -171,6 +171,20 @@ public struct Metadata: Sendable, Hashable { self.elements.append(.init(key: key, value: value)) } + /// Add the contents of a `Sequence` of key-value pairs to this `Metadata` instance. + /// + /// - Parameter other: the `Sequence` whose key-value pairs should be added into this `Metadata` instance. + public mutating func add(contentsOf other: some Sequence) { + self.elements.append(contentsOf: other.map(KeyValuePair.init)) + } + + /// Add the contents of another `Metadata` to this instance. + /// + /// - Parameter other: the `Metadata` whose key-value pairs should be added into this one. + public mutating func add(contentsOf other: Metadata) { + self.elements.append(contentsOf: other.elements) + } + /// Removes all values associated with the given key. /// /// - Parameter key: The key for which all values should be removed. diff --git a/Tests/GRPCCoreTests/MetadataTests.swift b/Tests/GRPCCoreTests/MetadataTests.swift index f0b29df04..68c3df85c 100644 --- a/Tests/GRPCCoreTests/MetadataTests.swift +++ b/Tests/GRPCCoreTests/MetadataTests.swift @@ -252,4 +252,66 @@ struct MetadataTests { #expect(self.metadata == ["key1": "value1", "key3": "value1"]) } } + + @Suite("Merge") + struct Merge { + var metadata: Metadata = [ + "key1": "value1-1", + "key2": "value2", + "key3": "value3", + ] + var otherMetadata: Metadata = [ + "key4": "value4", + "key5": "value5", + ] + + @Test("Where key is already present with a different value") + mutating func mergeWhereKeyIsAlreadyPresentWithDifferentValue() async throws { + self.otherMetadata.addString("value1-2", forKey: "key1") + self.metadata.add(contentsOf: self.otherMetadata) + + #expect( + self.metadata == [ + "key1": "value1-1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5", + "key1": "value1-2", + ] + ) + } + + @Test("Where key is already present with same value") + mutating func mergeWhereKeyIsAlreadyPresentWithSameValue() async throws { + self.otherMetadata.addString("value1-1", forKey: "key1") + self.metadata.add(contentsOf: self.otherMetadata) + + #expect( + self.metadata == [ + "key1": "value1-1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5", + "key1": "value1-1", + ] + ) + } + + @Test("Where key is not already present") + mutating func mergeWhereKeyIsNotAlreadyPresent() async throws { + self.metadata.add(contentsOf: self.otherMetadata) + + #expect( + self.metadata == [ + "key1": "value1-1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5", + ] + ) + } + } }