diff --git a/Sources/HTTPTypes/HTTPFields.swift b/Sources/HTTPTypes/HTTPFields.swift index 905aacc..eb5cd3c 100644 --- a/Sources/HTTPTypes/HTTPFields.swift +++ b/Sources/HTTPTypes/HTTPFields.swift @@ -293,9 +293,21 @@ extension HTTPFields: Equatable { extension HTTPFields: Hashable { public func hash(into hasher: inout Hasher) { + // Equality ignores the order of differently named fields, so hashing + // must too. Combine each name's sequence (order of same-named fields + // still matters), then mix those group hashes commutatively. + var grouped = [String: Hasher]() for field in self.fields { - hasher.combine(field) + let key = field.name.canonicalName + grouped[key, default: Hasher()].combine(field) } + var combined = 0 + for nameHasher in grouped.values { + // HTTPField already hashes its name, so XOR the per-name sequence hashes directly. + combined ^= nameHasher.finalize() + } + hasher.combine(combined) + hasher.combine(self.fields.count) } } diff --git a/Tests/HTTPTypesTests/HTTPTypesTests.swift b/Tests/HTTPTypesTests/HTTPTypesTests.swift index 3ee9e87..5423762 100644 --- a/Tests/HTTPTypesTests/HTTPTypesTests.swift +++ b/Tests/HTTPTypesTests/HTTPTypesTests.swift @@ -279,11 +279,7 @@ extension HTTPField.Name { .acceptEncoding: "gzip", ] #expect(fields1 == fields2) - - // Equal values must therefore hash equally. - withKnownIssue("HTTPFields.hash(into:) is order sensitive while == is not") { - #expect(fields1.hashValue == fields2.hashValue) - } + #expect(fields1.hashValue == fields2.hashValue) } @Test func sendable() {