2727
2828import 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
3232public enum WebAPIRequestMethod : String {
3333 case Get = " GET "
@@ -52,6 +52,7 @@ public struct WebAPIHeaderValue {
5252
5353public 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
102103public 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