-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAPIExample.swift
153 lines (125 loc) · 6.06 KB
/
APIExample.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//
// APIExample.swift
// CracksJournal
//
// Created by Guerson on 2020-04-08.
// Copyright © 2020 Guerson. All rights reserved.
//
import Foundation
/// API Class encharged of executing all api requests.
/// - parameter rest: Response type, this is the class that the response should be converted to. Ex: User.self. The class should conform to Codable protocol.
/// - parameter url: Request url.
/// - parameter params: The body of the request. The class should conform to Codable protocol.
/// - parameter method: POST, PUT, DELETE, GET, etc...
/// - parameter handler: Function called after the request completes the DataTask.
/// There are many advantages on having a single API request method and controlling what happens on this method. Ex: We can add authentication headers or parameters that all requests need without having lots of duplicated call on each API call.
class APIRequest {
func performRequest<R: Codable, P: Codable>(rest: R.Type, url: String, parms: P?, heads: [String: String]?, method: RIRequestMethod, handler: APIResponse<R>.APIRequestHandler??) -> URLSessionDataTask {
let encodedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let requestUrl: URL = URL(string: encodedUrl!)!
var request: URLRequest = URLRequest(url: requestUrl)
request.httpMethod = method.rawValue
if let heads = heads {
for (key, value) in heads {
request.addValue(value, forHTTPHeaderField: key)
}
}
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Accept-Encoding", forHTTPHeaderField: "gzip")
if let parms = parms {
let encoder = JSONEncoder()
if let jsonBody = try? encoder.encode(parms) {
request.httpBody = jsonBody
} else {
let error = NSError(domain: "com.rise.risefit", code: 0, userInfo: [NSLocalizedDescriptionKey: "ERR: RIRequest Unable to parse params to JSON"])
var resObj: APIResponse<R> = APIResponse()
resObj.error = error
handler?(resObj)
}
}
let configurarion = URLSessionConfiguration.default
let session = URLSession(configuration: configurarion, delegate: nil, delegateQueue: nil)
let task = session.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
var resObj: APIResponse<R> = APIResponse()
let isSuccess = response.statusCode().isSuccessCode()
if let data = data {
let decoder = JSONDecoder()
do {
/// If res is succes parse success response object
if isSuccess {
let obj = try decoder.decode(rest, from: data)
resObj.data = obj
}
/// If not success try to parse an errorObject
else {
let errObj = try decoder.decode(RFApiError.self, from: data)
resObj.apiErr = errObj
print("ApiRequest: Assigning api error Url: \(url)")
errObj.printObj()
}
} catch {
print("ApiRequestErr: Error parsing res \(error)")
resObj.error = error
}
handler?(resObj)
} else if let error = error {
resObj.error = error
handler?(resObj)
} else {
let unknownError = NSError(domain: "api.request.error", code: 0, userInfo: [NSLocalizedDescriptionKey: "ERR: ApiRequest DataTask Unknown error"])
resObj.error = unknownError
handler?(resObj)
}
}
}
task.resume()
return task
}
}
/// Object returned for a network request.
/// data is the object returned by the API in case of a successfull response.
/// in case of an error, the error parameter will contain the API error.
struct APIResponse<T: Codable> {
typealias APIRequestHandler = (_ response: RIResp<T>) -> Void
var data: T?
var error: Error?
}
/// Model Protocol. All model objects must conform to this protocol.
/// This example requires an id field but we can add all fields we require on out model layer. This is very helpful to extend the Model class and add functionality to all of our objects.
protocol Model: Codable {
var id: String? { get set}
}
/// Example of Model protocol extension. This function checks wheater two model objects are equal by comparing their id parameter.
extension Model {
static func == (lhs: Self, rhs: Self) -> Bool {
guard let lhsId = lhs.id, let rhsId = rhs.id else { return false }
return lhsId == rhsId
}
}
/// Example of a User model in our project.
/// Note: This is all the implementation we need in order to convert to JSON data, by extending Model: Codable protocol we inherit the ability to convert to JSON object.
/// Node: We can add nested models really easy as long as the objects conform to Codable as well.
struct User: Model {
var id: String?
var name: String?
var friends: [User]?
var userType: UserType?
}
enum UserType: String, Codable {
case free
case premium
}
/// Sample method for performing a GET/user request:
static func getSelf(_ handler: APIResponse<User>.APIRequestHandler?) -> URLSessionDataTask? {
let url = Routes.user()
return FJApi.get(rest: User.self, url: url) { (respnse) in
if let fetchedUser = response.data {
/// TODO: Do something with fetched user.
} else {
/// There was an error fetching the user. Should handler response.error
}
handler?(res)
}
}