compare error object return by alamofire - ios

I'm using Alamofire with EVReflection, in case responseObject fails to parse the raw response string into an object, an response.error will have some value, in case of a different error, a different value will be set.
Not sure how to compare those error values, to handle different error.
in case of JSON parsing error, print(error) will output
FAILURE: Error Domain=com.alamofirejsontoobjects.error Code=1 "Data could not be serialized. Input data was not json." UserInfo={NSLocalizedFailureReason=Data could not be serialized. Input data was not json.}
Alamofire.request(...)
.responseObject { (response: DataResponse<UserData>) in
guard response.error == nil else {
print(response.error)
return
}
}

When your request fails, you will get an error of type AFError from Alamofire. You can actually check AFError.swift file to get familiar with possible values. This file have really good documentation for every case.
Since AFError is an Error, which is of type enum, you can check like following:
switch err {
case .parameterEncodingFailed(let reason):
// do something with this.
// If you want to know more - check for reason's cases like
// switch reason {
// case .jsonEncodingFailed(let error):
// … // handle somehow
// case .propertyListEncodingFailed(let error):
// … // handle somehow
// }
case .responseValidationFailed(let reason):
// do something else with this
…
}
And for every reason you have some helper functions, so you can get even more info. Just check documentation.

Related

Handling errors properly in swift

I am trying to learn swift and I have hit a wall... I want to be able to switch the type of error i get back so I can do different things. It works fine in the .success but not in the .failure
exporter.export(progressHandler: { (progress) in
print(progress)
}, completionHandler: { result in
switch result {
case .success(let status):
switch status {
case .completed:
break
default:
break
}
break
case .failure(let error):
// I want to check what the error is
// e.g. the debugger says its "cancelled"
break
}
})
}
Can somebody help me with this?
Thanks
If you just want to see what happened, print the error object's localizedDescription.
print(error.localizedDescription)
If you have a decision to make, cast to NSError and examine the domain and code. That is more reliable though not as user-friendly. Only actual testing will tell you what the possible values are.
let error = error as NSError
if error.domain == ... && error.code == ... {
You can work out the corresponding Swift Error type by looking in the FoundationErrors.h header file. Once you do, you can refine your case structure to filter the error type into its own case:
case .failure(let error as NextLevelSessionExporterError):
// do something
case .failure(let error):
// do something else

Alamofire garbage at end error while parsing json

I'm connecting to my server on localhost to fetch some data. The data returned to this request is a JSON, on Postman the JSON is correctly shown, but when I'm playing with iOS, Alamofire returns me an error:
responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error
Domain=NSCocoaErrorDomain Code=3840 "Garbage at end."
UserInfo={NSDebugDescription=Garbage at end.}))
The JSON in question is:
{
"name": "TestName",
"surname": "TestSurname"
}
The thing that I do not understand is that if I force my server to return the json in form of a string so something like
"{"name": "TestName after update","surname": "TestSurname"}"
Alamofire does not complain and parses it correctly. How is that? I thought that specifying the parameter responseJSON it would have worked the other way around.
Alamofire.request("http://192.168.1.4:8080/user/abcdf").validate().responseJSON { response in
switch response.result {
case .success:
// DO stuff
case .failure(let error):
print(error)
}
}
This means your API response string is not a proper JSON. Ensure your response is valid JSON. In my case (below), JSON String had some HTML characters which broke the JSON.
If you are using Alamofire, change .responseJSON to .responseString and verify the response structure is valid JSON.
Note : if you are using Postman, you may not notice the extra unwanted character in JSON response. You need to change the response type from "Pretty" to "Raw" to observe this.
I think you need to get the data so you should have it written like this I am not sure though
Alamofire.request("http://192.168.1.4:8080/user/abcdf",method:.get).responseJSON {
response in
if response.result.isSuccess {
//do stuff
}
else {
// do other stuff
}
}

Trying to print a description of an Error (AKA ErrorType) enum

I am using an enum that inherits from Error (or ErrorType in Swift 2) and I am trying to use it in such a way that I can catch the error and use something like print(error.description) to print a description of the error.
This is what my Error enum looks like:
enum UpdateError: Error {
case NoResults
case UpdateInProgress
case NoSubredditsEnabled
case SetWallpaperError
var description: String {
switch self {
case .NoResults:
return "No results were found with the current size & aspect ratio constraints."
case .UpdateInProgress:
return "A wallpaper update was already in progress."
case .NoSubredditsEnabled:
return "No subreddits are enabled."
case .SetWallpaperError:
return "There was an error setting the wallpaper."
}
}
// One of many nested enums
enum JsonDownloadError: Error {
case TimedOut
case Offline
case Unknown
var description: String {
switch self {
case .TimedOut:
return "The request for Reddit JSON data timed out."
case .Offline:
return "The request for Reddit JSON data failed because the network is offline."
case .Unknown:
return "The request for Reddit JSON data failed for an unknown reason."
}
}
}
// ...
}
An important thing to note is that there are a few nested enums within UpdateError so something like this won't work because the nested enums aren't of the UpdateError type themselves:
do {
try functionThatThrowsUpdateError()
} catch {
NSLog((error as! UpdateError).description)
}
Is there a better way of printing a description of the error without having to check every type of UpdateError that occurred in the catch statement?
You could define another (possibly empty) protocol, and conform your errors to it.
protocol DescriptiveError {
var description : String { get }
}
// specify the DescriptiveError protocol in each enum
You could then pattern match against the protocol type.
do {
try functionThatThrowsUpdateError()
} catch let error as DescriptiveError {
print(error.description)
}

Using retryWhen to update tokens based on http error code

I found this example on How to refresh oauth token using moya and rxswift which I had to alter slightly to get to compile. This code works 80% for my scenario. The problem with it is that it will run for all http errors, and not just 401 errors. What I want is to have all my other http errors passed on as errors, so that I can handle them else where and not swallow them here.
With this code, if I get a HttpStatus 500, it will run the authentication code 3 times which is obviously not what I want.
Ive tried to alter this code to handle only handle 401 errors, but it seem that no matter what I do I can't get the code to compile. It's always complaining about wrong return type, "Cannot convert return expression of type Observable<Response> to return type Observable<Response>" which makes no sense to me..
What I want: handle 401, but stop on all other errors
import RxSwift
import KeychainAccess
import Moya
public extension ObservableType where E == Response {
/// Tries to refresh auth token on 401 errors and retry the request.
/// If the refresh fails, the signal errors.
public func retryWithAuthIfNeeded() -> Observable<E> {
return self.retryWhen {
(e: Observable<ErrorType>) in
return Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: { $1 })
.flatMap { i in
return AuthProvider.sharedInstance.request(
.LoginFacebookUser(
accessToken: AuthenticationManager.defaultInstance().getLoginTokenFromKeyChain(),
useFaceBookLogin: AuthenticationManager.defaultInstance().isFacebookLogin())
)
.filterSuccessfulStatusCodes()
.mapObject(Accesstoken.self)
.catchError {
error in
log.debug("ReAuth error: \(error)")
if case Error.StatusCode(let response) = error {
if response.statusCode == 401 {
// Force logout after failed attempt
log.debug("401:, force user logout")
NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
}
}
return Observable.error(error)
}.flatMapLatest({
token -> Observable<Accesstoken> in
AuthenticationManager.defaultInstance().storeServiceTokenInKeychain(token)
return Observable.just(token)
})
}
}
}
}
Compilation Error
Which line has the compilation error? It seems to me that it would be this line:
.catchError {
error in
//...
return Observable.error(error) // is this the line causing the compilation error?
}
If so, it's probably because catchError is expecting the block to return an Observable<Response> with which it can continue in case of an error, and not an Observable<ErrorType>.
In either case, it helps to annotate your code with more types so that you can pinpoint problems like this, as well as help the Swift compiler, which often can't figure out these kinds of things on its own. So something like this would have helped you:
.catchError {
error -> Observable<Response> in
//...
return Observable.error(error) // Swift should have a more accurate and helpful error message here now
}
Note that I'm only showing you what the error is and how to get Xcode to give you better error messages. What you're trying to return still isn't correct.
Only retry on 401
I'm not sure why you're expecting this code to treat 401 differently (other than posting to the notification center and logging). As it is, you're catching the error, but you're always returning an Observable with an Error event at the end (return Observable.error(error)), so it will never retry.
To get 401 to retry, you should return an Observable from the retryWhen block, which will send a Next event (signifying that you want to retry). For all other status codes, that Observable should send an Error (as you're currently doing), which will signify that you don't want to retry, and that you'd like the error propagated.
So something like this:
.retryWhen { errorObservable -> Observable<ErrorType> in
log.debug("ReAuth error: \(error)")
if case Error.StatusCode(let response) = error where response.statusCode == 401 {
log.debug("401:, force user logout")
NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
// If `401`, then return the `Observable<ErrorType>` which was given to us
// It will emit a `.Next<ErrorType>`
// Since it is a `.Next` event, `retryWhen` will retry.
return errorObservable
}
else {
// If not `401`, then `flatMap` the `Observable<ErrorType>` which
// is about to emit a `.Next<ErrorType>` into
// an `Observable<ErrorType>` which will instead emit a `.Error<ErrorType>`.
// Since it is an `.Error` event, `retryWhen` will *not* retry.
// Instead, it will propagate the error.
return errorObservable.flatMap { Observable.error($0) }
}
}
When you catchError, if it isn't a 401 error, then you simply need to throw the error. That will send the error down the pipe.
There's a different solution to solve this problem without using Observable. It's written on pure RxSwift and returns a classic error in case of fail.
The easy way to refresh session token of Auth0 with RxSwift and Moya
The main advantage of the solution is that it can be easily applicable for different services similar to Auth0 allowing to authenticate users in mobile apps.

How can I access the associated values of SwiftyDropbox errors?

I’ve been working with SwiftyDropbox and I’m having a curious problem with errors. Specifically, I’m not sure how to manipulate errors in the closure callbacks provided after responses are received so that I can get at their associated values.
For instance, the completion handler for Dropbox.authorizedClient.filesListFolder provides a
CallError<(Files.ListFolderError)>?
to work with. How would I go about checking if it is a
CallError.HTTPError
, so that I can get the HTTP error code out of it? Right now I’m just sucking that information out of the error’s .description but that doesn’t seem like the right way to do it.
This is what I’ve tried. I suspect I’m failing to understand something with the generics involved.
client.filesListFolder(path: "", recursive: false).response({ (listFolderResult, listFolderError) -> Void in
switch listFolderError {
case let .HTTPError(code, message, requestId):
print("http error")
default:
print("not a http error")
}
Enum case 'HTTPError' not found in type 'CallError?'
The problem here is that we're trying to switch on an optional. This simpler example highlights the exact same problem:
enum Foo {
case a
case b
}
let x: Foo? = nil
switch x {
case .a:
print("a")
case .b:
print("b")
}
Enum case 'a' not found in type 'Foo?'
We can switch over optionals because Optional is itself an Enum, with two cases: None and Some(T).
So when we're switching over an optional, Swift expects some code like this:
switch someOptional {
case .Some(someValue):
print("do some things")
case .None:
print("someOptional was nil")
}
But that's probably not necessarily particularly useful to use. We have an optional enum, and ultimately, if we dealt with our optional in a switch, we'd just have nested switch statements. Instead, we should deal with our optional in the normal Swift way of dealing with optionals:
if let error = listFolderError {
switch error {
case let .HTTPError(code, message, requestID):
print("http error")
default:
print("some other error")
}
} else {
print("there was no error")
}

Resources