Skip to content

Commit a8f89aa

Browse files
glbrnttweissi
authored andcommitted
Add a subscript to HPACKHeaders to return the first value with a matc… (#178)
* Add a subscript to HPACKHeaders to return the first value with a matching name Motivation: In some cases you only expect (or care about) a single value for a header. However, getting this value without allocating an array or traversing the whole array requres using first(where:) provided by Sequence. This is not very ergonomic as it requires users to do the case-insensitive comparison and then exctact the value from the HPACKHeader. Additionally, users cannot use NIO HTTP/2s case insensitive ASCII comparison. Modifications: - Provide a first(name:) method on HPACKHeaders which returns the value from the first header whose name is a case insentive match of the subscript parameter. Result: It's easier to get the first
1 parent ac157bc commit a8f89aa

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

Sources/NIOHPACK/HPACKHeader.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,27 @@ public struct HPACKHeaders: ExpressibleByDictionaryLiteral {
161161

162162
return array
163163
}
164-
165-
/// Checks if a header is present
164+
165+
/// Retrieves the first value for a given header field name from the block.
166+
///
167+
/// This method uses case-insensitive comparisons for the header field name. It
168+
/// does not return the first value from a maximally-decomposed list of the header fields,
169+
/// but instead returns the first value from the original representation: that means
170+
/// that a comma-separated header field list may contain more than one entry, some of
171+
/// which contain commas and some do not. If you want a representation of the header fields
172+
/// suitable for performing computation on, consider `getCanonicalForm`.
173+
///
174+
/// - Parameter name: The header field name whose first value should be retrieved.
175+
/// - Returns: The first value for the header field name.
176+
public func first(name: String) -> String? {
177+
guard !self.headers.isEmpty else {
178+
return nil
179+
}
180+
181+
return self.headers.first { $0.name.isEqualCaseInsensitiveASCIIBytes(to: name) }?.value
182+
}
183+
184+
/// Checks if a header is present.
166185
///
167186
/// - parameters:
168187
/// - name: The name of the header

Tests/NIOHPACKTests/HPACKCodingTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ extension HPACKCodingTests {
3434
("testInlineDynamicTableResize", testInlineDynamicTableResize),
3535
("testHPACKHeadersDescription", testHPACKHeadersDescription),
3636
("testHPACKHeadersSubscript", testHPACKHeadersSubscript),
37+
("testHPACKHeadersFirst", testHPACKHeadersFirst),
3738
("testHPACKHeadersExpressedByDictionaryLiteral", testHPACKHeadersExpressedByDictionaryLiteral),
3839
("testHPACKHeadersAddingSequenceOfPairs", testHPACKHeadersAddingSequenceOfPairs),
3940
("testHPACKHeadersAddingOtherHPACKHeaders", testHPACKHeadersAddingOtherHPACKHeaders),

Tests/NIOHPACKTests/HPACKCodingTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,20 @@ class HPACKCodingTests: XCTestCase {
581581
XCTAssertEqual(headers[canonicalForm: "set-cookie"], ["abcdefg,hijklmn,opqrst"])
582582
}
583583

584+
func testHPACKHeadersFirst() throws {
585+
let headers = HPACKHeaders([
586+
(":method", "GET"),
587+
("foo", "bar"),
588+
("foo", "baz"),
589+
("custom-key", "value-1,value-2")
590+
])
591+
592+
XCTAssertEqual(headers.first(name: ":method"), "GET")
593+
XCTAssertEqual(headers.first(name: "Foo"), "bar")
594+
XCTAssertEqual(headers.first(name: "custom-key"), "value-1,value-2")
595+
XCTAssertNil(headers.first(name: "not-present"))
596+
}
597+
584598
func testHPACKHeadersExpressedByDictionaryLiteral() throws {
585599
let headers: HPACKHeaders = [
586600
":method": "GET",

0 commit comments

Comments
 (0)