/
Contents.swift
125 lines (102 loc) · 3.33 KB
/
Contents.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import Foundation
import PlaygroundSupport
enum HTTPMethod: String {
case get = "GET"
case put = "PUT"
case post = "POST"
case delete = "DELETE"
case head = "HEAD"
case options = "OPTIONS"
case trace = "TRACE"
case connect = "CONNECT"
}
struct HTTPHeader {
let field: String
let value: String
}
class APIRequest {
let method: HTTPMethod
let path: String
var queryItems: [URLQueryItem]?
var headers: [HTTPHeader]?
var body: Data?
init(method: HTTPMethod, path: String) {
self.method = method
self.path = path
}
init<Body: Encodable>(method: HTTPMethod, path: String, body: Body) throws {
self.method = method
self.path = path
self.body = try JSONEncoder().encode(body)
}
}
struct APIResponse<Body> {
let statusCode: Int
let body: Body
}
extension APIResponse where Body == Data? {
func decode<BodyType: Decodable>(to type: BodyType.Type) throws -> APIResponse<BodyType> {
guard let data = body else {
throw APIError.decodingFailure
}
let decodedJSON = try JSONDecoder().decode(BodyType.self, from: data)
return APIResponse<BodyType>(statusCode: self.statusCode,
body: decodedJSON)
}
}
enum APIError: Error {
case invalidURL
case requestFailed
case decodingFailure
}
enum APIResult<Body> {
case success(APIResponse<Body>)
case failure(APIError)
}
struct APIClient {
typealias APIClientCompletion = (APIResult<Data?>) -> Void
private let session = URLSession.shared
private let baseURL = URL(string: "https://jsonplaceholder.typicode.com")!
func perform(_ request: APIRequest, _ completion: @escaping APIClientCompletion) {
var urlComponents = URLComponents()
urlComponents.scheme = baseURL.scheme
urlComponents.host = baseURL.host
urlComponents.path = baseURL.path
urlComponents.queryItems = request.queryItems
guard let url = urlComponents.url?.appendingPathComponent(request.path) else {
completion(.failure(.invalidURL)); return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = request.method.rawValue
urlRequest.httpBody = request.body
request.headers?.forEach { urlRequest.addValue($0.value, forHTTPHeaderField: $0.field) }
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(.requestFailed)); return
}
completion(.success(APIResponse<Data?>(statusCode: httpResponse.statusCode, body: data)))
}
task.resume()
}
}
struct Post: Decodable {
let userId: Int
let id: Int
let title: String
let body: String
}
let request = APIRequest(method: .get, path: "posts")
APIClient().perform(request) { (result) in
switch result {
case .success(let response):
if let response = try? response.decode(to: [Post].self) {
let posts = response.body
print("Received posts: \(posts.first?.title ?? "")")
} else {
print("Failed to decode response")
}
case .failure:
print("Error perform network request")
}
}
PlaygroundPage.current.needsIndefiniteExecution = true