Skip to content

Commit 5068a6d

Browse files
committed
Initial commit
1 parent c75e876 commit 5068a6d

File tree

6 files changed

+187
-2
lines changed

6 files changed

+187
-2
lines changed

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGELOG

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# HTTPHeaders 0.x
2+
3+
## HTTPHeaders 0.0.1
4+
5+
*Dec 9, 2021*
6+
7+
- Initial relase

Package.swift

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// swift-tools-version:5.5
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "HTTPHeaders",
8+
platforms: [
9+
.macOS(.v10_15),
10+
.iOS(.v13),
11+
.tvOS(.v13),
12+
.watchOS(.v6)
13+
],
14+
products: [
15+
// Products define the executables and libraries a package produces, and make them visible to other packages.
16+
.library(
17+
name: "HTTPHeaders",
18+
targets: ["HTTPHeaders"]),
19+
],
20+
dependencies: [
21+
// Dependencies declare other packages that this package depends on.
22+
// .package(url: /* package url */, from: "1.0.0"),
23+
],
24+
targets: [
25+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
26+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
27+
.target(
28+
name: "HTTPHeaders",
29+
dependencies: []),
30+
.testTarget(
31+
name: "HTTPHeadersTests",
32+
dependencies: ["HTTPHeaders"]),
33+
]
34+
)

README.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,15 @@
1-
# Headers
2-
Parsing Simple HTTP Headers
1+
# HTTP Headers
2+
3+
Parsing simple HTTP headers.
4+
5+
Examples:
6+
7+
```swift
8+
let response = HTTPURLRseponse(..., headers: [
9+
"X-RateLimit-Reset": "2015-01-01T00:00:00Z"
10+
])
11+
12+
let header = HTTPHeader<Date>(field: "X-RateLimit-Reset")
13+
let date = try header.parse(from: response)
14+
// let date = try header.parseIfPresent(from: response)
15+
```

Sources/HTTPHeaders/HTTPHeader.swift

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2021 Alexander Grebenyuk (github.com/kean).
4+
5+
import Foundation
6+
7+
public struct HTTPHeader<T> {
8+
public let field: String
9+
private let _parse: (String) -> T?
10+
11+
public init(field: String, parse: @escaping (String) -> T?) {
12+
self.field = field
13+
self._parse = parse
14+
}
15+
16+
public func parse(from response: HTTPURLResponse) throws -> T {
17+
guard let value = response.value(forHTTPHeaderField: field) else {
18+
throw ParsingError.valueNotFound(field: field)
19+
}
20+
return try parse(value)
21+
}
22+
23+
public func parseIfPresent(from response: HTTPURLResponse) throws -> T? {
24+
guard let value = response.value(forHTTPHeaderField: field) else {
25+
return nil
26+
}
27+
return try parse(value)
28+
}
29+
30+
private func parse(_ value: String) throws -> T {
31+
guard let value = _parse(value) else {
32+
throw ParsingError.typeMismatch(field: field, value: value, type: T.self)
33+
}
34+
return value
35+
}
36+
37+
public enum ParsingError: Error {
38+
case valueNotFound(field: String)
39+
case typeMismatch(field: String, value: String, type: Any.Type)
40+
}
41+
}
42+
43+
extension HTTPHeader where T == String {
44+
public init(field: String) {
45+
self.init(field: field) { $0 }
46+
}
47+
}
48+
49+
extension HTTPHeader where T == Int {
50+
public init(field: String) {
51+
self.init(field: field) { Int($0) }
52+
}
53+
}
54+
55+
extension HTTPHeader where T == Date {
56+
public init(field: String, options: ISO8601DateFormatter.Options? = nil) {
57+
self.init(field: field) {
58+
let formatter = ISO8601DateFormatter()
59+
if let options = options {
60+
formatter.formatOptions = options
61+
}
62+
return formatter.date(from: $0)
63+
}
64+
}
65+
}
66+
67+
extension HTTPHeader where T == Double {
68+
public init(field: String) {
69+
self.init(field: field) { Double($0) }
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2021 Alexander Grebenyuk (github.com/kean).
4+
5+
import XCTest
6+
@testable import HTTPHeaders
7+
8+
final class HTTPHeadersTests: XCTestCase {
9+
func testParseString() throws {
10+
// Given
11+
let response = makeResponse(headers: ["X-RateLimit-Reset": "2015-01-01T00:00:00Z"])
12+
let header = HTTPHeader<String>(field: "X-RateLimit-Reset")
13+
14+
// When/Then
15+
let value = try header.parse(from: response)
16+
XCTAssertEqual(value, "2015-01-01T00:00:00.000Z")
17+
}
18+
19+
func testParseOptionalString() throws {
20+
// Given
21+
let response = makeResponse(headers: ["X-RateLimit-Reset": "2015-01-01T00:00:00Z"])
22+
let header = HTTPHeader<String>(field: "non-existing-key")
23+
24+
// When/Then
25+
let value = try header.parseIfPresent(from: response)
26+
XCTAssertNil(value)
27+
}
28+
29+
func testParseDate() throws {
30+
// Given
31+
let response = makeResponse(headers: ["X-RateLimit-Reset": "2015-01-01T00:00:00Z"])
32+
let header = HTTPHeader<Date>(field: "X-RateLimit-Reset")
33+
34+
// When/Then
35+
let value = try header.parse(from: response)
36+
XCTAssertEqual(value, Date.init(timeIntervalSinceReferenceDate: 441763200.0))
37+
}
38+
39+
func testParseDateWithCustomOptions() throws {
40+
// Given
41+
let response = makeResponse(headers: ["X-RateLimit-Reset": "2015-01-01T01:20:30.25Z"])
42+
let header = HTTPHeader<Date>(field: "X-RateLimit-Reset", options: [.withInternetDateTime, .withFractionalSeconds])
43+
44+
// When/Then
45+
let value = try header.parse(from: response)
46+
print(value.timeIntervalSinceReferenceDate)
47+
XCTAssertEqual(value, Date.init(timeIntervalSinceReferenceDate: 441768030.25))
48+
}
49+
50+
private func makeResponse(headers: [String: String]) -> HTTPURLResponse {
51+
HTTPURLResponse(url: URL(string: "https://api.github.com/user")!, statusCode: 200, httpVersion: nil, headerFields: headers)!
52+
}
53+
}

0 commit comments

Comments
 (0)