4

I am trying to do an API request to the backend using alamofire and responseDecodable.

AF.request(Router.registerFacebookUser(facebookToken: token)).validate().responseDecodable(of: UserConfig.self) { result in
        switch result.result {
        case let .success(userConfig):
            onAuthentication(userConfig)
        case let .failure(error):
            print(error)
            //somehow get the message from ERROR JSON and pass it here
            onFailure(error.localizedDescription)
        }
    }

When call succeeds, it successfully parses JSON to the model. However, there as some special cases, when it should fail. For example if user is already registered, I get a response JSON:

{
   "error":{
      "message":"User already exist"
   }
}

Is it possible to override the AF error that we receive? Or maybe it's possible to parse another object if request fails? Or are there other ways how I can access the error message?

AsMartynas
  • 159
  • 13
  • When it "fails", do you get a 200 with a different JSON kind, or do you have a different HTTP code? Then it depends if you need to work on the success or the failure case. – Larme Jul 06 '20 at 16:03
  • @Larme when it fails, I get the 400 and JSON for error. When it succeeds 200 and JSON for UserConfig – AsMartynas Jul 06 '20 at 16:08
  • You can use custom `validate()` https://stackoverflow.com/questions/45955823/what-is-the-use-of-the-validate-method-in-alamofire-request (in previous versions, but still should be available in newer versions). You then decide what to do: call success if error parsed is not nil, and not failure if not? Also, you should be able to retrieve the data from an `AFError`. Or just maybe access `result.data`? – Larme Jul 06 '20 at 16:11
  • `AFError` will not contain the response `Data`. You can create your own error to contain the parsed error response. – Jon Shier Jul 06 '20 at 18:59
  • @JonShier yeah, that's what I need, maybe you have more information how to do it? – AsMartynas Jul 06 '20 at 19:14

1 Answers1

9

There are several ways to approach this in Alamofire.

  1. In the validate() method that takes a closure, parse the error body and produce a .failure result with a custom associated error:
.validate { request, response, data
    // Check request or response state, parse data into a custom Error type.
    return .failure(MyCustomErrorType.case(parsedError))
}

Then, in your response handler, you'll need to cast to your custom error type from the AFError's underlyingError property:

.responseDecodable(of: SomeType.self) { response in
    switch response.result {
    case let .success(value): // Do something.
    case let .failure(error):
        let customError = error.underlyingError as? MyCustomErrorType
        // Do something with the error, like extracting the associated value.
}
  1. Use a Decodable container type to parse your responses as either the type you expect or your error representation. You should be able to find examples elsewhere, but on Alamofire's side it would work like this:
.responseDecodable(of: ContainerType<SomeType>.self) { response in
    // Do something with response.
}
  1. Write a custom ResponseSerializer type that checks the response and parses the error type when a failure is detected, otherwise parsing the expected type. We have examples in our documentation.

Of these options I usually go with the wrapper type unless I'm already using my own custom Error type, in which case the validator is fairly easy. A custom serializer is the most work but gives you the most flexibility as well, especially if you need to customize other aspects of your response handling.

Jon Shier
  • 12,200
  • 3
  • 35
  • 37