Browse Source

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.
Gus Cairo 1 year ago
parent
commit
d5e0d70480
2 changed files with 76 additions and 0 deletions
  1. 14 0
      Sources/GRPCCore/Metadata.swift
  2. 62 0
      Tests/GRPCCoreTests/MetadataTests.swift

+ 14 - 0
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<Element>) {
+    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.

+ 62 - 0
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",
+        ]
+      )
+    }
+  }
 }