Skip to content

Commit

Permalink
Fixes #44.
Browse files Browse the repository at this point in the history
  • Loading branch information
ashfurrow committed Sep 12, 2014
1 parent 0e3b65e commit 9cc79da
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 71 deletions.
4 changes: 2 additions & 2 deletions Endpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import Alamofire

/// Used for stubbing responses.
public enum EndpointSampleResponse {
case Success(NSData)
case Error(NSError?)
case Success(Int, NSData)
case Error(Int?, NSError?)
}

/// Class for reifying a target of the T enum unto a concrete Endpoint
Expand Down
24 changes: 22 additions & 2 deletions Moya+ReactiveCocoa.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@

import Foundation

public class MoyaResponse {
public let statusCode: Int
public let data: NSData

public init(statusCode: Int, data: NSData) {
self.statusCode = statusCode
self.data = data
}
}

extension MoyaResponse: Printable, DebugPrintable {
public var description: String {
return "Status Code: \(statusCode), Data Length: \(data.length)"
}

public var debugDescription: String {
return description
}
}

/// Subclass of MoyaProvider that returns RACSignal instances when requests are made. Much better than using completion closures.
public class ReactiveMoyaProvider<T where T: MoyaTarget>: MoyaProvider<T> {
/// Current requests that have not completed or errored yet.
Expand All @@ -30,12 +50,12 @@ public class ReactiveMoyaProvider<T where T: MoyaTarget>: MoyaProvider<T> {
// weak self just for best practices – RACSignal will take care of any retain cycles anyway,
// and we're connecting immediately (below), so self in the block will always be non-nil
let signal = RACSignal.createSignal({ [weak self] (subscriber) -> RACDisposable! in
self?.request(token, method: method, parameters: parameters) { (data: NSData?, error: NSError?) -> () in
self?.request(token, method: method, parameters: parameters) { (data, statusCode, error) -> () in
if let error = error {
subscriber.sendError(error)

This comment has been minimized.

Copy link
@powerje

powerje Sep 12, 2014

Contributor

Is there a way to get the status code out of the error here? I rarely need status codes from successful web requests, but they are completely necessary in error cases.

Would it make sense to wrap errors that come back with a new error that replaces the error code with the http status code? Or can we pull the error code out of the userInfo of the NSError?

} else {
if let data = data {
subscriber.sendNext(data)
subscriber.sendNext(MoyaResponse(statusCode: statusCode!, data: data))
}
subscriber.sendCompleted()
}
Expand Down
17 changes: 9 additions & 8 deletions Moya.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
import Alamofire

/// Block to be executed when a request has completed.
public typealias MoyaCompletion = (data: NSData?, error: NSError?) -> ()
public typealias MoyaCompletion = (data: NSData?, statusCode: Int?, error: NSError?) -> ()

/// General-purpose class to store some enums and class funcs.
public class Moya {
Expand Down Expand Up @@ -108,21 +108,22 @@ public class MoyaProvider<T: MoyaTarget> {
// Need to dispatch to the next runloop to give the subject a chance to be subscribed to (useful for unit tests)
dispatch_async(dispatch_get_main_queue(), {
switch endpoint.sampleResponse {
case .Success(let data):
completion(data: data, error: nil)
case .Error(let error):
completion(data: nil, error: error)
case .Success(let statusCode, let data):
completion(data: data, statusCode: statusCode, error: nil)
case .Error(let statusCode, let error):
completion(data: nil, statusCode: statusCode, error: error)
}
})
} else {
Alamofire.Manager.sharedInstance.request(request)
.response({(request: NSURLRequest, reponse: NSHTTPURLResponse?, data: AnyObject?, error: NSError?) -> () in
.response({(request: NSURLRequest, response: NSHTTPURLResponse?, data: AnyObject?, error: NSError?) -> () in
// Alamofire always sense the data param as an NSData? type, but we'll
// add a check just in case something changes in the future.
let statusCode = response?.statusCode
if let data = data as? NSData {
completion(data: data, error: error)
completion(data: data, statusCode: statusCode, error: error)
} else {
completion(data: nil, error: error)
completion(data: nil, statusCode: statusCode, error: error)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion Moya/MoyaTests/EndpointSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class EndpointSpec: QuickSpec {
let target: GitHub = .Zen
let parameters = ["Nemesis": "Harvey"] as [String: AnyObject]
let headerFields = ["Title": "Dominar"] as [String: AnyObject]
endpoint = Endpoint<GitHub>(URL: url(target), sampleResponse: .Success(target.sampleData), method: Moya.Method.GET, parameters: parameters, parameterEncoding: .JSON, httpHeaderFields: headerFields)
endpoint = Endpoint<GitHub>(URL: url(target), sampleResponse: .Success(200, target.sampleData), method: Moya.Method.GET, parameters: parameters, parameterEncoding: .JSON, httpHeaderFields: headerFields)
})

it("returns a new endpoint for endpointByAddingParameters") {
Expand Down
36 changes: 24 additions & 12 deletions Moya/MoyaTests/MoyaProviderSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class MoyaProviderSpec: QuickSpec {
var message: String?

let target: GitHub = .Zen
provider.request(target, completion: { (data, error) in
provider.request(target, completion: { (data, statusCode, error) in
if let data = data {
message = NSString(data: data, encoding: NSUTF8StringEncoding)
}
Expand All @@ -38,7 +38,7 @@ class MoyaProviderSpec: QuickSpec {
var message: String?

let target: GitHub = .UserProfile("ashfurrow")
provider.request(target, completion: { (data, error) in
provider.request(target, completion: { (data, statusCode, error) in
if let data = data {
message = NSString(data: data, encoding: NSUTF8StringEncoding)
}
Expand Down Expand Up @@ -73,7 +73,7 @@ class MoyaProviderSpec: QuickSpec {

it("executes the endpoint resolver") {
let target: GitHub = .Zen
provider.request(target, completion: { (data, error) in })
provider.request(target, completion: { (data, statusCode, error) in })

let sampleData = target.sampleData as NSData
expect{executed}.toEventually(beTruthy(), timeout: 1, pollInterval: 0.1)
Expand All @@ -86,13 +86,25 @@ class MoyaProviderSpec: QuickSpec {
provider = ReactiveMoyaProvider(endpointsClosure: endpointsClosure, stubResponses: true)
}

it("returns a MoyaResponse object") {
var called = false

provider.request(.Zen).subscribeNext({ (object) -> Void in
if let response = object as? MoyaResponse {
called = true
}
})

expect{called}.toEventuallyNot(beNil(), timeout: 1, pollInterval: 0.1)
}

it("returns stubbed data for zen request") {
var message: String?

let target: GitHub = .Zen
provider.request(target).subscribeNext({ (data) -> Void in
if let data = data as? NSData {
message = NSString(data: data, encoding: NSUTF8StringEncoding)
provider.request(target).subscribeNext({ (object) -> Void in
if let response = object as? MoyaResponse {
message = NSString(data: response.data, encoding: NSUTF8StringEncoding)
}
})

Expand All @@ -101,18 +113,18 @@ class MoyaProviderSpec: QuickSpec {
}

it("returns correct data for user profile request") {
var response: NSDictionary?
var receivedResponse: NSDictionary?

let target: GitHub = .UserProfile("ashfurrow")
provider.request(target).subscribeNext({ (object) -> Void in
if let data = object as? NSData {
response = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? NSDictionary
if let response = object as? MoyaResponse {
receivedResponse = NSJSONSerialization.JSONObjectWithData(response.data, options: nil, error: nil) as? NSDictionary
}
})

let sampleData = target.sampleData as NSData
let sampleResponse: NSDictionary = NSJSONSerialization.JSONObjectWithData(sampleData, options: nil, error: nil) as NSDictionary
expect{response}.toEventuallyNot(beNil(), timeout: 1, pollInterval: 0.1)
expect{receivedResponse}.toEventuallyNot(beNil(), timeout: 1, pollInterval: 0.1)
}

it("returns identical signals for inflight requests") {
Expand All @@ -139,7 +151,7 @@ class MoyaProviderSpec: QuickSpec {
var errored = false

let target: GitHub = .Zen
provider.request(target, completion: { (object, error) in
provider.request(target, completion: { (object, statusCode, error) in
if error != nil {
errored = true
}
Expand All @@ -153,7 +165,7 @@ class MoyaProviderSpec: QuickSpec {
var errored = false

let target: GitHub = .UserProfile("ashfurrow")
provider.request(target, completion: { (object, error) in
provider.request(target, completion: { (object, statusCode, error) in
if error != nil {
errored = true
}
Expand Down
83 changes: 48 additions & 35 deletions Moya/MoyaTests/RACSignal+MoyaSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ extension UIImage {
}
}

func signalSendingData(data: NSData?) -> RACSignal {
func signalSendingData(data: NSData, statusCode: Int = 200) -> RACSignal {
return RACSignal.createSignal({ (subscriber) -> RACDisposable! in
subscriber.sendNext(data)
subscriber.sendNext(MoyaResponse(statusCode: statusCode, data: data))
subscriber.sendCompleted()

return nil
Expand All @@ -32,6 +32,48 @@ func signalSendingData(data: NSData?) -> RACSignal {

class RACSignalMoyaSpec: QuickSpec {
override func spec() {
describe("status codes filtering", {
it("filters out unrequested status codes") {
let data = NSData()
let signal = signalSendingData(data, statusCode: 10)

var errored = false
signal.filterStatusCodes(0...9).subscribeNext({ (object) -> Void in
XCTFail("called on non-correct status code: \(object)")
}, error: { (error) -> Void in
errored = true
})

expect{errored}.toEventually(beTruthy(), timeout: 1.0, pollInterval: 0.1)
}

it("filters out non-successful status codes") {
let data = NSData()
let signal = signalSendingData(data, statusCode: 404)

var errored = false
signal.filterSuccessfulStatusCodes().subscribeNext({ (object) -> Void in
XCTFail("called on non-success status code: \(object)")
}, error: { (error) -> Void in
errored = true
})

expect{errored}.toEventually(beTruthy(), timeout: 1.0, pollInterval: 0.1)
}

it("passes through correct status codes") {
let data = NSData()
let signal = signalSendingData(data)

var called = false
signal.filterSuccessfulStatusCodes().subscribeNext({ (object) -> Void in
called = true
})

expect{called}.toEventually(beTruthy(), timeout: 1.0, pollInterval: 0.1)
}
})

describe("image maping", {
it("maps data representing an image to an image") {
let image = UIImage.testPNGImage(named: "testImage")
Expand All @@ -57,16 +99,15 @@ class RACSignalMoyaSpec: QuickSpec {
receivedError = error
})

expect(receivedError).toNot(beNil())
expect(receivedError?.code).to(equal(MoyaErrorCode.ImageMapping.toRaw()))
}
})

describe("JSON mapping", { () -> () in
it("maps data representing some JSON to that JSON") {
let json = ["name": "John Crighton", "occupation": "Astronaut"]
let data = NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions.PrettyPrinted, error: nil)
let signal = signalSendingData(data)
let data = NSJSONSerialization.dataWithJSONObject(json, options: nil, error: nil)
let signal = signalSendingData(data!)

var receivedJSON: [String: String]?
signal.mapJSON().subscribeNext({ (json) -> Void in
Expand All @@ -82,7 +123,7 @@ class RACSignalMoyaSpec: QuickSpec {
it("returns a Cocoa error domain for invalid JSON") {
let json = "{ \"name\": \"john }"
let data = json.dataUsingEncoding(NSUTF8StringEncoding)
let signal = signalSendingData(data)
let signal = signalSendingData(data!)

var receivedError: NSError?
signal.mapJSON().subscribeNext({ (image) -> Void in
Expand All @@ -94,27 +135,13 @@ class RACSignalMoyaSpec: QuickSpec {
expect(receivedError).toNot(beNil())
expect(receivedError?.domain).to(equal(NSCocoaErrorDomain))
}

it("ignores missing data") {
let signal = signalSendingData(nil)

var receivedError: NSError?
signal.mapJSON().subscribeNext({ (image) -> Void in
XCTFail("next called for invalid data")
}, error: { (error) -> Void in
receivedError = error
})

expect(receivedError).toNot(beNil())
expect(receivedError?.code).to(equal(MoyaErrorCode.JSONMapping.toRaw()))
}
})

describe("string mapping", { () -> () in
it("maps data representing a string to a string") {
let string = "You have the rights to the remains of a silent attorney."
let data = string.dataUsingEncoding(NSUTF8StringEncoding)
let signal = signalSendingData(data)
let signal = signalSendingData(data!)

var receivedString: String?
signal.mapString().subscribeNext({ (string) -> Void in
Expand All @@ -124,20 +151,6 @@ class RACSignalMoyaSpec: QuickSpec {

expect(receivedString).to(equal(string))
}

it("ignores missing strings") {
let signal = signalSendingData(nil)

var receivedError: NSError?
signal.mapString().subscribeNext({ (string) -> Void in
XCTFail("next called for invalid data")
}, error: { (error) -> Void in
receivedError = error
})

expect(receivedError).toNot(beNil())
expect(receivedError?.code).to(equal(MoyaErrorCode.StringMapping.toRaw()))
}
})
}
}
4 changes: 2 additions & 2 deletions Moya/MoyaTests/TestResources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ public func url(route: MoyaTarget) -> String {
}

let endpointsClosure = { (target: GitHub, method: Moya.Method, parameters: [String: AnyObject]) -> Endpoint<GitHub> in
return Endpoint<GitHub>(URL: url(target), sampleResponse: .Success(target.sampleData), method: method, parameters: parameters)
return Endpoint<GitHub>(URL: url(target), sampleResponse: .Success(200, target.sampleData), method: method, parameters: parameters)
}

let failureEndpointsClosure = { (target: GitHub, method: Moya.Method, parameters: [String: AnyObject]) -> Endpoint<GitHub> in
return Endpoint<GitHub>(URL: url(target), sampleResponse: .Error(NSError()), method: method, parameters: parameters)
return Endpoint<GitHub>(URL: url(target), sampleResponse: .Error(nil, NSError()), method: method, parameters: parameters)
}

0 comments on commit 9cc79da

Please sign in to comment.