Skip to content

Commit 2e5dc8b

Browse files
committed
Expanded WebApi to include access to Http Response object
1 parent 4a85544 commit 2e5dc8b

File tree

2 files changed

+69
-23
lines changed

2 files changed

+69
-23
lines changed

CodeQuickKit/CodeQuickKitTests/WebAPITests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class WebAPITests: XCTestCase {
1414
return
1515
}
1616

17-
let injectedResponse = WebAPIInjectedResponse(statusCode: 200, responseObject: ["name": "Mock Me"], error: nil, timeout: 2)
17+
let injectedResponse = WebAPIInjectedResponse(statusCode: 200, response: nil, responseObject: ["name": "Mock Me"], error: nil, timeout: 2)
1818
webApi.injectedResponses["http://www.example.com/api/test"] = injectedResponse
1919
}
2020

@@ -26,7 +26,7 @@ class WebAPITests: XCTestCase {
2626
func testInjectedResponse() {
2727
let expectation = expectationWithDescription("Injected Response")
2828

29-
api!.get("test", queryItems: nil) { (statusCode, responseObject, error) -> Void in
29+
api!.get("test", queryItems: nil) { (statusCode, response, responseObject, error) -> Void in
3030
XCTAssertTrue(statusCode == 200)
3131
guard let dictionary = responseObject as? [String : String] else {
3232
XCTFail()

Sources/Foundation/WebAPI.swift

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
import Foundation
2929

30-
public typealias WebAPICompletion = (statusCode: Int, responseObject: AnyObject?, error: NSError?) -> Void
30+
public typealias WebAPICompletion = (statusCode: Int, response: NSHTTPURLResponse?, responseObject: AnyObject?, error: NSError?) -> Void
3131

3232
public enum WebAPIRequestMethod: String {
3333
case Get = "GET"
@@ -52,6 +52,7 @@ public struct WebAPIHeaderValue {
5252

5353
public struct WebAPIInjectedResponse {
5454
public var statusCode: Int = 0
55+
public var response: NSHTTPURLResponse?
5556
public var responseObject: AnyObject?
5657
public var error: NSError?
5758
public var timeout: UInt64 = 0
@@ -98,7 +99,7 @@ public enum WebAPIError: ErrorType {
9899
/// A wrapper for NSURLSession for communication with JSON REST API's
99100
/// ### Features
100101
/// - automatic deserialization of a JSON response
101-
/// - mockability with injected responses
102+
/// - testability with injected responses
102103
public class WebAPI {
103104

104105
public var baseURL: NSURL?
@@ -118,26 +119,26 @@ public class WebAPI {
118119
// MARK: - Convenience Methods
119120

120121
public final func get(path: String, queryItems: [NSURLQueryItem]? = nil, completion: WebAPICompletion) {
121-
execute(path, queryItems: queryItems, method: .Get, data: nil, completion: completion)
122+
execute(path: path, queryItems: queryItems, method: .Get, data: nil, completion: completion)
122123
}
123124

124125
public final func put(data: NSData?, path: String, queryItems: [NSURLQueryItem]? = nil, completion: WebAPICompletion) {
125-
execute(path, queryItems: queryItems, method: .Put, data: data, completion: completion)
126+
execute(path: path, queryItems: queryItems, method: .Put, data: data, completion: completion)
126127
}
127128

128129
public final func post(data: NSData?, path: String, queryItems: [NSURLQueryItem]? = nil, completion: WebAPICompletion) {
129-
execute(path, queryItems: queryItems, method: .Post, data: data, completion: completion)
130+
execute(path: path, queryItems: queryItems, method: .Post, data: data, completion: completion)
130131
}
131132

132133
public final func delete(path: String, queryItems: [NSURLQueryItem]? = nil, completion: WebAPICompletion) {
133-
execute(path, queryItems: queryItems, method: .Delete, data: nil, completion: completion)
134+
execute(path: path, queryItems: queryItems, method: .Delete, data: nil, completion: completion)
134135
}
135136

136137
// MARK: - Request Setup
137138

138139
/// Constructs the request, setting the method, body data, and headers based on parameters
139140
/// Subclasses can override this method to customize the request as needed.
140-
public func requestFor(path: String, queryItems: [NSURLQueryItem]?, method: WebAPIRequestMethod, data: NSData?) -> NSMutableURLRequest? {
141+
public func request(forPath path: String, queryItems: [NSURLQueryItem]?, method: WebAPIRequestMethod, data: NSData?) -> NSMutableURLRequest? {
141142
guard let baseURL = self.baseURL else {
142143
return nil
143144
}
@@ -162,19 +163,20 @@ public class WebAPI {
162163
// MARK: - Execution
163164

164165
/// Executes the request returned from `requestFor(path:queyItems:method:data:)`
165-
public final func execute(path: String, queryItems: [NSURLQueryItem]?, method: WebAPIRequestMethod, data: NSData?, completion: WebAPICompletion) {
166-
if let request = self.requestFor(path, queryItems: queryItems, method: method, data: data) {
167-
execute(request, completion: completion)
168-
} else {
169-
completion(statusCode: 0, responseObject: nil, error: WebAPIError.InvalidRequest.error)
166+
public final func execute(path path: String, queryItems: [NSURLQueryItem]?, method: WebAPIRequestMethod, data: NSData?, completion: WebAPICompletion) {
167+
guard let request = self.request(forPath: path, queryItems: queryItems, method: method, data: data) else {
168+
completion(statusCode: 0, response: nil, responseObject: nil, error: WebAPIError.InvalidRequest.error)
169+
return
170170
}
171+
172+
execute(request, completion: completion)
171173
}
172174

173175
/// Transforms the request into a `multipart/form-data` request.
174176
/// The request `content-type` will be set to `image/png` and the associated filename will be `image.png`
175177
public final func execute(path: String, queryItems: [NSURLQueryItem]?, method: WebAPIRequestMethod, pngImageData: NSData, completion: WebAPICompletion) {
176178
Logger.verbose("\(#function)", callingClass: self.dynamicType)
177-
if let request = requestFor(path, queryItems: queryItems, method: method, data: nil) {
179+
if let request = request(forPath: path, queryItems: queryItems, method: method, data: nil) {
178180
let boundary = NSUUID().UUIDString.stringByReplacingOccurrencesOfString("-", withString: "")
179181
let contentType = "multipart/form-data; boundary=\(boundary)"
180182
request.setValue(contentType, forHTTPHeaderField: WebAPIHeaderKey.ContentType)
@@ -205,46 +207,90 @@ public class WebAPI {
205207

206208
execute(request, completion: completion)
207209
} else {
208-
completion(statusCode: 0, responseObject: nil, error: WebAPIError.InvalidRequest.error)
210+
completion(statusCode: 0, response: nil, responseObject: nil, error: WebAPIError.InvalidRequest.error)
211+
}
212+
}
213+
214+
private func task(forRequest request: NSMutableURLRequest, completion: WebAPICompletion) -> NSURLSessionDataTask {
215+
return session.dataTaskWithRequest(request) { (responseData: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
216+
dispatch_async(dispatch_get_main_queue(), { () -> Void in
217+
guard error == nil else {
218+
completion(statusCode: 0, response: nil, responseObject: nil, error: error)
219+
return
220+
}
221+
222+
let httpResponse = response as! NSHTTPURLResponse
223+
224+
guard let data = responseData else {
225+
completion(statusCode: httpResponse.statusCode, response : httpResponse, responseObject: nil, error: error)
226+
return
227+
}
228+
229+
guard data.length != 0 else {
230+
completion(statusCode: httpResponse.statusCode, response: httpResponse, responseObject: nil, error: error)
231+
return
232+
}
233+
234+
if let contentType = httpResponse.allHeaderFields[WebAPIHeaderKey.ContentType] {
235+
guard contentType.hasPrefix(WebAPIHeaderValue.ApplicationJson) else {
236+
completion(statusCode: httpResponse.statusCode, response: httpResponse, responseObject: nil, error: error)
237+
return
238+
}
239+
}
240+
241+
var body: AnyObject?
242+
var e: NSError? = error
243+
do {
244+
body = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)
245+
} catch {
246+
if e == nil {
247+
e = (error as NSError)
248+
} else {
249+
print(error)
250+
}
251+
}
252+
253+
completion(statusCode: httpResponse.statusCode, response: httpResponse, responseObject: body, error: e)
254+
})
209255
}
210256
}
211257

212258
private func execute(request: NSMutableURLRequest, completion: WebAPICompletion) {
213259
Logger.verbose("\(#function)", callingClass: self.dynamicType)
214260
guard let url = request.URL else {
215-
completion(statusCode: 0, responseObject: nil, error: WebAPIError.InvalidURL.error)
261+
completion(statusCode: 0, response: nil, responseObject: nil, error: WebAPIError.InvalidURL.error)
216262
return
217263
}
218264

219265
if let canned = injectedResponses[url.absoluteString] {
220266
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(canned.timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), { () -> Void in
221-
completion(statusCode: canned.statusCode, responseObject: canned.responseObject, error: canned.error)
267+
completion(statusCode: canned.statusCode, response: canned.response, responseObject: canned.responseObject, error: canned.error)
222268
})
223269
return
224270
}
225271

226272
session.dataTaskWithRequest(request) { (responseData: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
227273
dispatch_async(dispatch_get_main_queue(), { () -> Void in
228274
guard error == nil else {
229-
completion(statusCode: 0, responseObject: nil, error: error)
275+
completion(statusCode: 0, response: nil, responseObject: nil, error: error)
230276
return
231277
}
232278

233279
let httpResponse = response as! NSHTTPURLResponse
234280

235281
guard let data = responseData else {
236-
completion(statusCode: httpResponse.statusCode, responseObject: nil, error: error)
282+
completion(statusCode: httpResponse.statusCode, response : httpResponse, responseObject: nil, error: error)
237283
return
238284
}
239285

240286
guard data.length != 0 else {
241-
completion(statusCode: httpResponse.statusCode, responseObject: nil, error: error)
287+
completion(statusCode: httpResponse.statusCode, response: httpResponse, responseObject: nil, error: error)
242288
return
243289
}
244290

245291
if let contentType = httpResponse.allHeaderFields[WebAPIHeaderKey.ContentType] {
246292
guard contentType.hasPrefix(WebAPIHeaderValue.ApplicationJson) else {
247-
completion(statusCode: httpResponse.statusCode, responseObject: nil, error: error)
293+
completion(statusCode: httpResponse.statusCode, response: httpResponse, responseObject: nil, error: error)
248294
return
249295
}
250296
}
@@ -261,7 +307,7 @@ public class WebAPI {
261307
}
262308
}
263309

264-
completion(statusCode: httpResponse.statusCode, responseObject: body, error: e)
310+
completion(statusCode: httpResponse.statusCode, response: httpResponse, responseObject: body, error: e)
265311
})
266312
}.resume()
267313
}

0 commit comments

Comments
 (0)