Skip to content

Commit 594c2c4

Browse files
authored
feat(request): Add a Request type (#2)
1 parent a4374d6 commit 594c2c4

16 files changed

+310
-22
lines changed

Sources/Pinata/DataCoder.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Foundation
2+
3+
/// A encoder suited to encode to Data
4+
public protocol DataEncoder {
5+
func encode<T: Encodable>(_ value: T) throws -> Data
6+
}
7+
8+
/// A decoder suited to decode Data
9+
public protocol DataDecoder {
10+
func decode<T: Decodable>(_ type: T.Type, from: Data) throws -> T
11+
}
12+
13+
/// A `DataEncoder` providing a `ContentType`
14+
public protocol ContentDataEncoder: DataEncoder {
15+
/// a http content type
16+
static var contentType: HTTPContentType { get }
17+
}
18+
19+
/// A `DataDecoder` providing a `ContentType`
20+
public protocol ContentDataDecoder: DataDecoder {
21+
/// a http content type
22+
static var contentType: HTTPContentType { get }
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Foundation
2+
3+
extension Encodable {
4+
/// Encode the object with provided encoder.
5+
/// This technique allow to "open" an existential, that is to use it in a context where a generic is expected
6+
func encoded(with encoder: DataEncoder) throws -> Data {
7+
try encoder.encode(self)
8+
}
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Foundation
2+
3+
extension JSONEncoder: ContentDataEncoder {
4+
public static let contentType = HTTPContentType.json
5+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Foundation
2+
3+
#if canImport(FoundationNetworking)
4+
import FoundationNetworking
5+
#endif
6+
7+
extension URL {
8+
init<Output>(from request: Request<Output>) throws {
9+
guard var components = URLComponents(string: request.path) else {
10+
throw URLComponents.Error.invalid(path: request.path)
11+
}
12+
13+
let queryItems = (components.queryItems ?? []) + request.parameters.queryItems
14+
15+
components.queryItems = queryItems.isEmpty ? nil : queryItems
16+
17+
guard let url = components.url else {
18+
throw URLComponents.Error.cannotGenerateURL(components: components)
19+
}
20+
21+
self = url
22+
}
23+
}
24+
25+
extension URLComponents {
26+
enum Error: Swift.Error {
27+
case invalid(path: String)
28+
case cannotGenerateURL(components: URLComponents)
29+
}
30+
}
31+
32+
extension Dictionary where Key == String, Value == String {
33+
fileprivate var queryItems: [URLQueryItem] {
34+
map { URLQueryItem(name: $0.key, value: $0.value) }
35+
}
36+
}

Sources/Pinata/Foundation/URLRequest+Encode.swift

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Foundation
2+
3+
#if canImport(FoundationNetworking)
4+
import FoundationNetworking
5+
#endif
6+
7+
public extension URLRequest {
8+
func encodedBody(_ body: Encodable, encoder: ContentDataEncoder) throws -> Self {
9+
var request = self
10+
11+
try request.encodeBody(body, encoder: encoder)
12+
13+
return request
14+
}
15+
16+
/// Use a `Encodable` object as request body and set the "Content-Type" header associated to the encoder
17+
mutating func encodeBody(_ body: Encodable, encoder: ContentDataEncoder) throws {
18+
httpBody = try body.encoded(with: encoder)
19+
setHeaders([.contentType: type(of: encoder).contentType.value])
20+
}
21+
22+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Foundation
2+
3+
#if canImport(FoundationNetworking)
4+
import FoundationNetworking
5+
#endif
6+
7+
extension URLRequest {
8+
public mutating func setHeaders(_ headers: HTTPHeaderFields) {
9+
for (header, value) in headers {
10+
setValue(value, forHTTPHeaderField: header.key)
11+
}
12+
}
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Foundation
2+
3+
/// A struct representing a http header content type value
4+
public struct HTTPContentType: Hashable, ExpressibleByStringLiteral {
5+
let value: String
6+
7+
public init(value: String) {
8+
self.value = value
9+
}
10+
11+
public init(stringLiteral value: StringLiteralType) {
12+
self.value = value
13+
}
14+
}
15+
16+
extension HTTPContentType {
17+
public static let json: Self = "application/json"
18+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
3+
/// HTTP headers `Dictionary` and their associated value
4+
public typealias HTTPHeaderFields = [HTTPHeader: String]
5+
6+
/// A struct representing a http request header key
7+
public struct HTTPHeader: Hashable, ExpressibleByStringLiteral {
8+
public let key: String
9+
10+
public init(stringLiteral value: StringLiteralType) {
11+
self.key = value
12+
}
13+
}
14+
15+
extension HTTPHeader {
16+
public static let accept: Self = "Accept"
17+
public static let authentication: Self = "Authentication"
18+
public static let contentType: Self = "Content-Type"
19+
}
20+
21+
@available(*, unavailable, message: "This is a reserved header. See https://developer.apple.com/documentation/foundation/nsurlrequest#1776617")
22+
extension HTTPHeader {
23+
public static let authorization: Self = "Authorization"
24+
public static let connection: Self = "Connection"
25+
public static let contentLength: Self = "Content-Length"
26+
public static let host: Self = "Host"
27+
public static let proxyAuthenticate: Self = "Proxy-Authenticate"
28+
public static let proxyAuthorization: Self = "Proxy-Authorization"
29+
public static let wwwAuthenticate: Self = "WWW-Authenticate"
30+
}

Sources/Pinata/Request/Path.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Foundation
2+
3+
/// A Type representing a URL path
4+
public protocol Path {
5+
var path: String { get }
6+
}
7+
8+
extension Path where Self: RawRepresentable, RawValue == String {
9+
public var path: String { rawValue }
10+
}
11+
12+
extension String: Path {
13+
public var path: String { self }
14+
}

0 commit comments

Comments
 (0)