Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kean committed Dec 10, 2021
1 parent c75e876 commit 5068a6d
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 2 deletions.
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# HTTPHeaders 0.x

## HTTPHeaders 0.0.1

*Dec 9, 2021*

- Initial relase
34 changes: 34 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "HTTPHeaders",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "HTTPHeaders",
targets: ["HTTPHeaders"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "HTTPHeaders",
dependencies: []),
.testTarget(
name: "HTTPHeadersTests",
dependencies: ["HTTPHeaders"]),
]
)
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
# Headers
Parsing Simple HTTP Headers
# HTTP Headers

Parsing simple HTTP headers.

Examples:

```swift
let response = HTTPURLRseponse(..., headers: [
"X-RateLimit-Reset": "2015-01-01T00:00:00Z"
])

let header = HTTPHeader<Date>(field: "X-RateLimit-Reset")
let date = try header.parse(from: response)
// let date = try header.parseIfPresent(from: response)
```
71 changes: 71 additions & 0 deletions Sources/HTTPHeaders/HTTPHeader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// The MIT License (MIT)
//
// Copyright (c) 2021 Alexander Grebenyuk (github.com/kean).

import Foundation

public struct HTTPHeader<T> {
public let field: String
private let _parse: (String) -> T?

public init(field: String, parse: @escaping (String) -> T?) {
self.field = field
self._parse = parse
}

public func parse(from response: HTTPURLResponse) throws -> T {
guard let value = response.value(forHTTPHeaderField: field) else {
throw ParsingError.valueNotFound(field: field)
}
return try parse(value)
}

public func parseIfPresent(from response: HTTPURLResponse) throws -> T? {
guard let value = response.value(forHTTPHeaderField: field) else {
return nil
}
return try parse(value)
}

private func parse(_ value: String) throws -> T {
guard let value = _parse(value) else {
throw ParsingError.typeMismatch(field: field, value: value, type: T.self)
}
return value
}

public enum ParsingError: Error {
case valueNotFound(field: String)
case typeMismatch(field: String, value: String, type: Any.Type)
}
}

extension HTTPHeader where T == String {
public init(field: String) {
self.init(field: field) { $0 }
}
}

extension HTTPHeader where T == Int {
public init(field: String) {
self.init(field: field) { Int($0) }
}
}

extension HTTPHeader where T == Date {
public init(field: String, options: ISO8601DateFormatter.Options? = nil) {
self.init(field: field) {
let formatter = ISO8601DateFormatter()
if let options = options {
formatter.formatOptions = options
}
return formatter.date(from: $0)
}
}
}

extension HTTPHeader where T == Double {
public init(field: String) {
self.init(field: field) { Double($0) }
}
}
53 changes: 53 additions & 0 deletions Tests/HTTPHeadersTests/HTTPHeadersTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// The MIT License (MIT)
//
// Copyright (c) 2021 Alexander Grebenyuk (github.com/kean).

import XCTest
@testable import HTTPHeaders

final class HTTPHeadersTests: XCTestCase {
func testParseString() throws {
// Given
let response = makeResponse(headers: ["X-RateLimit-Reset": "2015-01-01T00:00:00Z"])
let header = HTTPHeader<String>(field: "X-RateLimit-Reset")

// When/Then
let value = try header.parse(from: response)
XCTAssertEqual(value, "2015-01-01T00:00:00.000Z")
}

func testParseOptionalString() throws {
// Given
let response = makeResponse(headers: ["X-RateLimit-Reset": "2015-01-01T00:00:00Z"])
let header = HTTPHeader<String>(field: "non-existing-key")

// When/Then
let value = try header.parseIfPresent(from: response)
XCTAssertNil(value)
}

func testParseDate() throws {
// Given
let response = makeResponse(headers: ["X-RateLimit-Reset": "2015-01-01T00:00:00Z"])
let header = HTTPHeader<Date>(field: "X-RateLimit-Reset")

// When/Then
let value = try header.parse(from: response)
XCTAssertEqual(value, Date.init(timeIntervalSinceReferenceDate: 441763200.0))
}

func testParseDateWithCustomOptions() throws {
// Given
let response = makeResponse(headers: ["X-RateLimit-Reset": "2015-01-01T01:20:30.25Z"])
let header = HTTPHeader<Date>(field: "X-RateLimit-Reset", options: [.withInternetDateTime, .withFractionalSeconds])

// When/Then
let value = try header.parse(from: response)
print(value.timeIntervalSinceReferenceDate)
XCTAssertEqual(value, Date.init(timeIntervalSinceReferenceDate: 441768030.25))
}

private func makeResponse(headers: [String: String]) -> HTTPURLResponse {
HTTPURLResponse(url: URL(string: "https://api.github.com/user")!, statusCode: 200, httpVersion: nil, headerFields: headers)!
}
}

0 comments on commit 5068a6d

Please sign in to comment.