MLSDev/TRON

Better way of getting error when using TRON with RxSwift.

Closed this issue · 2 comments

Hello, I'm wondering if there is a better approach to avoid force casting error while using RxSwift.

MyApiClient.Token.for(user: "username", pass: "superpassword").subscribe(onNext: { token in
      dump(token)
}, onError: { error in
      guard let error = error as? APIError<MyApiError> else { return }
      dump(error.errorModel)
}).addDisposableTo(disposeBag)

I'm seeing the wrapper around APIRequest https://github.com/MLSDev/TRON/blob/master/Source/Tron%2BRxSwift.swift#L43

failure: is being implicitly called with APIError<ErrorModel> as parameter, and that parameter is passed to observer.onError, but if you see my code above, I need to force cast since I'm getting a generic Error, since rxResult() -> RxSwift.Observable<Model>

Basically, we are losing the concrete APIError<ErrorModel> type

any clue?

MyApiClient.Token implementation is like

enum MyApiClient {}
extension MyApiClient {
    private static let tron = TRON(baseURL: Environment.baseUrl)

    struct Token {
        private static let clientId = "IOS-CLIENT"
        private enum grantType {
            case `private`, `public`
        }

        final class Model: JSONDecodable {
            let token: String
            let expiresIn: Int
            
            required init(json: JSON) {
                token = json["token"].stringValue
                expiresIn = json["expiresIn"].intValue
            }
        }
        
        static func `for`(user: String, pass: String) -> Observable<Model> {
            let request : APIRequest<Model, TripBotApiError> = tron.request("tokens")
            request.method = .post
            request.parameters = [
                "clientId": Token.clientId,
                "grantType": Token.grantType.private,
                "username": user,
                "password": pass
            ]
            return request.rxResult()
        }
    }
}

You have essentially described RxSwift error handling =) Currently it always loses error type parameter. We've considered several options, for example we could use https://github.com/antitypical/Result to save Error generic parameter, but it felt weird bringing another dependency just to preserve type in simple Rx extension. Alamofire also has Result, but it also loses error type parameter, so that would not be helpful too.

For now we decided to leave the things as it is, because current extension is simple enough and viable in the beginning. More advanced users can easily implement their own Rx extensions on top of current API to have more flexibility. For example, here is how to convert APIRequest to Observable<Result,Error>:

extension APIRequest where ErrorModel : JSONDecodable {
    func mapToResult() -> Observable<Result<Model,APIError<ErrorModel>>> {
        return Observable.create({ observer in
            weak var token = self.perform(withSuccess: { model in
                observer.onNext(.success(model))
            }, failure: { error in
                observer.onNext(.failure(error))
            })
            return Disposables.create {
                if let token = token, !(token.task?.state == .canceling) {
                    token.cancel()
                }
            }
        })
    }
}

This way you are not using typed errors, if you need them(but of course, you need to add Result framework to your app to achieve this).

Hope this information will be helpful.

@DenHeadless it is helpful indeed. Thanks for your response.