Skip to content

Commit 3ac078f

Browse files
authored
add HTTPHeaders.first(name:) (#37)
* add `HTTPHeaders.first(name:)` * use explicit `UTF8View` instead of generic sequence of bytes
1 parent 1afa339 commit 3ac078f

File tree

2 files changed

+89
-1
lines changed

2 files changed

+89
-1
lines changed

Sources/AWSLambdaEvents/Utils/HTTP.swift

+58-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,68 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
// MARK: HTTPMethod
15+
// MARK: HTTPHeaders
1616

1717
public typealias HTTPHeaders = [String: String]
1818
public typealias HTTPMultiValueHeaders = [String: [String]]
1919

20+
extension HTTPHeaders {
21+
/// Retrieves the first value for a given header-field / dictionary-key (`name`) from the block.
22+
/// This method uses case-insensitive comparisons.
23+
///
24+
/// - Parameter name: The header field name whose first value should be retrieved.
25+
/// - Returns: The first value for the header field name.
26+
public func first(name: String) -> String? {
27+
guard !self.isEmpty else {
28+
return nil
29+
}
30+
31+
return self.first { header in header.0.isEqualCaseInsensitiveASCIIBytes(to: name) }?.1
32+
}
33+
}
34+
35+
extension String {
36+
internal func isEqualCaseInsensitiveASCIIBytes(to: String) -> Bool {
37+
self.utf8.compareCaseInsensitiveASCIIBytes(to: to.utf8)
38+
}
39+
}
40+
41+
extension String.UTF8View {
42+
/// Compares the collection of `UInt8`s to a case insensitive collection.
43+
///
44+
/// This collection could be get from applying the `UTF8View`
45+
/// property on the string protocol.
46+
///
47+
/// - Parameter bytes: The string constant in the form of a collection of `UInt8`
48+
/// - Returns: Whether the collection contains **EXACTLY** this array or no, but by ignoring case.
49+
internal func compareCaseInsensitiveASCIIBytes(to: String.UTF8View) -> Bool {
50+
// fast path: we can get the underlying bytes of both
51+
let maybeMaybeResult = self.withContiguousStorageIfAvailable { lhsBuffer -> Bool? in
52+
to.withContiguousStorageIfAvailable { rhsBuffer in
53+
if lhsBuffer.count != rhsBuffer.count {
54+
return false
55+
}
56+
57+
for idx in 0 ..< lhsBuffer.count {
58+
// let's hope this gets vectorised ;)
59+
if lhsBuffer[idx] & 0xDF != rhsBuffer[idx] & 0xDF {
60+
return false
61+
}
62+
}
63+
return true
64+
}
65+
}
66+
67+
if let maybeResult = maybeMaybeResult, let result = maybeResult {
68+
return result
69+
} else {
70+
return self.elementsEqual(to, by: { ($0 & 0xDF) == ($1 & 0xDF) })
71+
}
72+
}
73+
}
74+
75+
// MARK: HTTPMethod
76+
2077
public struct HTTPMethod: RawRepresentable, Equatable {
2178
public var rawValue: String
2279

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AWSLambdaEvents
16+
import XCTest
17+
18+
class HTTPHeadersTests: XCTestCase {
19+
func first() throws {
20+
let headers: HTTPHeaders = [
21+
":method": "GET",
22+
"foo": "bar",
23+
"custom-key": "value-1,value-2",
24+
]
25+
26+
XCTAssertEqual(headers.first(name: ":method"), "GET")
27+
XCTAssertEqual(headers.first(name: "Foo"), "bar")
28+
XCTAssertEqual(headers.first(name: "custom-key"), "value-1,value-2")
29+
XCTAssertNil(headers.first(name: "not-present"))
30+
}
31+
}

0 commit comments

Comments
 (0)