Commit 5946d7e4 authored by Mauro E. Bender's avatar Mauro E. Bender

Close #2 - Create resource item

Add error handling.
parent c1085960
......@@ -13,6 +13,12 @@ public protocol JSONSerializable {
func toJSON() -> [ String: AnyObject ]
}
public enum ApiError: ErrorType {
case Network( error: NSError, response: NSURLResponse )
case JSONSerialization(error: NSError)
case Unknown( error: NSError )
}
public class Resource<T: JSONSerializable> {
public let api: Api
public let path: String
......@@ -23,13 +29,44 @@ public class Resource<T: JSONSerializable> {
}
public func item( id: AnyObject, parameters: [ String: String ]? = nil,
headers: [ String: String]? = nil, success: ( ( T ) -> Void ) )
headers: [ String: String]? = nil, completion: ( ( T?, error: ApiError? ) -> Void ) )
{
api.get( resourceMemberPath( id ), parameters: parameters, headers: headers ) { response in
if let json = response.result.value {
success( T( fromJSON: json as! [String : AnyObject] ) )
}
self.handleItemResult( response, handler: completion )
}
}
public func create( item: T, parameters: [ String: String ]? = nil,
headers: [ String: String]? = nil, encoding: ParameterEncoding = .JSON,
completion: ( ( T?, error: ApiError? ) -> Void ) )
{
var requestParameters = item.toJSON()
if parameters != nil { requestParameters += parameters! }
api.post( path, parameters: requestParameters, encoding: encoding, headers: headers ) { response in
self.handleItemResult( response, handler: completion )
}
}
func handleItemResult( response: Response<AnyObject, NSError>, handler: ( T?, error: ApiError? ) -> Void ) {
switch(response.result) {
case .Success(let JSON):
handler( T( fromJSON: JSON as! [String : AnyObject] ), error: nil )
case .Failure(let error):
handler( nil, error: handleError( response ) )
}
}
func handleError( response: Response<AnyObject, NSError> ) -> ApiError {
let error = response.result.error!
if let statusCode = response.response?.statusCode where !(200...299).contains( statusCode ) {
return .Network( error: error, response: response.response! )
} else if error.code == -6006 {
return .JSONSerialization( error: error )
}
return .Unknown( error: error )
}
func resourceMemberPath( memberId: AnyObject ) -> String {
......
//
// Utils.swift
// Pods
//
// Created by Mauro Bender on 14/8/16.
//
//
import Foundation
func += <K, V> (inout left: [K:V], right: [K:V]) {
for (k, v) in right {
left.updateValue(v, forKey: k)
}
}
\ No newline at end of file
......@@ -20,6 +20,17 @@ extension Item: JSONSerializable {
}
func toJSON() -> [String : AnyObject] {
return [ String: AnyObject ]()
var json = [ String: AnyObject ]()
if id > 0 { json[ "id" ] = id }
json[ "name" ] = name
return json
}
}
extension Item: Equatable {}
func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.id == rhs.id && lhs.name == rhs.name
}
\ No newline at end of file
......@@ -10,6 +10,31 @@ import Api
import Foundation
import Mockingjay
// FIX HTTPBody being empty on matcher/builder
extension NSInputStream {
func readFully() -> NSData {
let result = NSMutableData()
self.open()
readTo(result)
self.close()
return result
}
private func readTo(data: NSMutableData) {
var buffer = [UInt8](count: 4096, repeatedValue: 0)
var amount = 0
repeat {
amount = self.read(&buffer, maxLength: buffer.count)
if amount > 0 {
data.appendBytes(buffer, length: amount)
}
} while amount > 0
}
}
let baseURL = NSURL( string: "http://api.test.com" )!
let apiPath = "1"
......@@ -18,4 +43,31 @@ func matchApiPath( path: String, method: ApiMethod = .GET ) -> ( ( NSURLRequest
return { ( request: NSURLRequest ) in
return url == request.URL && request.HTTPMethod == method.rawValue
}
}
func buildItemFromRequest( id: Int ) -> ( (request: NSURLRequest) -> Response ) {
return { ( request: NSURLRequest ) in
let response = NSHTTPURLResponse(URL: request.URL!, statusCode: 200, HTTPVersion: nil, headerFields: nil)!
return .Success(response, createItemWithIDFromRequest( id, request: request ) )
}
}
func createItemWithIDFromRequest( id: Int, request: NSURLRequest ) -> NSData? {
var json = jsonFromRequest( request )
guard json != nil else { return nil }
json![ "id" ] = id
return try? NSJSONSerialization.dataWithJSONObject( json!, options: .PrettyPrinted )
}
func jsonFromRequest( request: NSURLRequest ) -> [ String: AnyObject ]? {
guard let body = request.HTTPBodyStream?.readFully() else { return nil }
let json = try? NSJSONSerialization.JSONObjectWithData( body, options: .AllowFragments )
guard json != nil else { return nil }
return json! as? [ String: AnyObject ]
}
\ No newline at end of file
......@@ -17,6 +17,8 @@ class ResourceTests: QuickSpec {
// Register stubs
stub( matchApiPath( "items/1" ), builder: json( [ "id": 1, "name": "Item 1" ] ) )
stub( matchApiPath( "items", method: .POST ), builder: buildItemFromRequest( 2 ) )
stub( matchApiPath( "failedItems", method: .POST ), builder: http( 422 ) )
}
override func spec() {
......@@ -25,13 +27,44 @@ class ResourceTests: QuickSpec {
describe( "Resource" ) {
let resource: Resource<Item> = api.resource( "items" )
describe( "item<T>" ) {
describe( "item" ) {
context( "when the request is successful" ) {
it( "should call the correct endpoint and return the item" ) {
waitUntil { done in
resource.item( 1 ) { ( item: Item ) in
expect( item.id ) == 1
expect( item.name ) == "Item 1"
resource.item( 1 ) { ( item: Item?, error: ApiError? ) in
expect( item?.id ) == 1
expect( item?.name ) == "Item 1"
done()
}
}
}
}
}
describe( "create" ) {
context( "when the request is successful" ) {
let itemToCreate = Item( id: 0, name: "Item 2" )
it( "should call the correct endpoint and return the created item" ) {
waitUntil { done in
resource.create( itemToCreate ) { ( item: Item?, error: ApiError? ) in
expect( item?.id ) == 2
expect( item?.name ) == "Item 2"
done()
}
}
}
}
context( "when there's an error" ) {
let failResource : Resource<Item> = api.resource( "failedItems" )
let itemToCreate = Item( id: 0, name: "Item 2" )
it( "should call the correct endpoint and return the created item" ) {
waitUntil { done in
failResource.create( itemToCreate ) { ( item: Item?, error: ApiError? ) in
expect( item ).to( beNil() )
expect( error ).toNot( beNil() )
done()
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment