I'm using Moya with RxSwift for networking in an iOS app, and I'd like to be able to consume my API's custom error responses when my Observer
s get calls to onError
.
The API always returns error responses in the following JSON format:
{
"error": {
"message": "Error message goes here",
"code": "String error code"
}
}
The goal is to achieve something similar to the following code snippet, where the error passed in onError
is my custom error type instead of the Moya.Error
type:
observable
.subscribe(
onNext: { response in
// Do typical onNext stuff
},
onError: { error in
// Get and consume my custom error type here:
let customError = error as! MyCustomErrorModel
print("My API's error message: \(customError.error?.message)")
print("My API's error code: \(customError.error?.code)")
})
I'm able to successfully intercept and deserialize these errors into my custom error model using a custom PluginType
(pasted below, from this SO question), but I do not know how to finally pass these models along to the Observer
.
import Foundation
import Moya
import ObjectMapper
import Result
struct CustomAPIErrorPlugin: PluginType {
// Called immediately before a request is sent over the network (or stubbed).
func willSendRequest(request: RequestType, target: TargetType) { }
// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
func didReceiveResponse(result: Result<Moya.Response, Moya.Error>, target: TargetType) {
let responseJSON: AnyObject
if let response = result.value {
do {
responseJSON = try response.mapJSON()
if let errorResponse = Mapper<MyCustomErrorModel>().map(responseJSON) {
print("Custom error code from server: \(errorResponse.error?.code)")
}
} catch {
print("Failure to parse JSON response")
}
} else {
print("Network Error = \(result.error)")
}
}
I would suggest to extend the ObservableType since that ends up being the cleanest solution when we talk about handling api error responses. Something as below (not tested ...)
extension ObservableType where E == Response {
func filterSuccess() -> Observable<E> {
return flatMap { (response) -> Observable<E> in
if 200 ... 299 ~= response.statusCode {
return Observable.just(response)
}
if let errorJson = response.data.toJson(),
let error = Mapper<MyCustomErrorModel>().map(errorJson) {
return Observable.error(error)
}
// Its an error and can't decode error details from server, push generic message
let genericError = MyCustomErrorModel.genericError(code: response.statusCode, message: "Unknown Error")
return Observable.error(genericError)
}
}
This is how you use it
provider.request(.test)
.filterSuccess()
.mapJSON()
.subscribe { [unowned self] e in
switch e {
case .next(let json as JSON):
case .error(let error as MyCustomErrorModel):
// Handle your custom error here
default: break
}
}.disposed(by: disposeBag)