sean7512 / RestEssentials

Lightweight REST and JSON library for Swift.
MIT License
37 stars 12 forks source link

Access error message #27

Closed vipatronon closed 4 years ago

vipatronon commented 4 years ago

When I receive an error from my server, I also receive an error message in the response body as a string, but I'm not able to get it... Is there any way to access it? I'm calling the service like this:

var restOptions = RestOptions() restOptions.expectedStatusCode = 200

rest.get([BundleVersionsResponse].self, options: restOptions) { result, httpResponse in
    do {
        let response = try result.value()
        print(response.count)
    }
    catch {
        print("Error performing GET: \(error)")
    }
}
sean7512 commented 4 years ago

@vipatronon Please see the section on Error Handling in the Readme: https://github.com/sean7512/RestEssentials#error-handling You need to catch the different types of errors that may be thrown. Different error types may come with different associated data, etc.

From your description, you're likely getting a badResponse, malformedResponse or its a Codable Error since the string can't be deserialized to your object. If it is either badResponse or malformedResponse the Data object returned is the raw Data off the wire, you can convert it to a String if you need (and are sure it is a string).

sean7512 commented 4 years ago

@vipatronon were you able to get this working or is there a missing feature somewhere that prevents you from using it?

vipatronon commented 4 years ago

Hi @sean7512

I totally forgot to answer this thread, and yes, I was able to solve this issue

Thank you!

yulius-fxpal commented 4 years ago

Is there a way to get the raw data or print it somehow out when we can a DecodingError exception? I got a DecodingError.keyNotFound and would like to see the original data being parsed.

https://developer.apple.com/documentation/swift/decodingerror/keynotfound There is no way of doing that in the context from what i can tell if there is a Codable parse error

sean7512 commented 4 years ago

From your link, "As associated values, this case contains the attempted key and context for debugging." It does tell you the key that is missing.

yulius-fxpal commented 4 years ago

I would like to see the original JSON message if parsing a codable. It tells me the missing key but if there's a bug and another JSON object type is sent, I wouldn't be able to see what was the JSON that caused the error.

For example if I'm expecting a car but get a pet, I would get an error that says only color is missing. I want to debug and inspect the data to see that I'm receiving a pet object and I should change my code accordingly.

struct Car {
   let type:String
   let color:String
   let doors:Int
   ...
}

struct Pet {
  let type:String
  let breed:String
  ...
}

I'm unable to access label or result value while in the debugger and an exception has occurred. https://github.com/sean7512/RestEssentials/blob/f87bacc/Sources/RestEssentials/RestController.swift#L175

sean7512 commented 4 years ago

If it really is a decodable error, then result.value() will work and the returned data is the raw Data object. You will have to convert to a string manually, this is the raw bytes.

It could be exposed if RestEssentials API error took a holder class that was Data? HTTPURLResponse? and it could pass it both in on an error. The way it is now, the data gets lost as the Codable error is raised and we re-throw it. This is a breaking change though and would have to be for a new major release.

yulius-fxpal commented 4 years ago

Thank you for your quick responses.

Unfortunately DecodingError doesn't have the original data payload when I catch it. Here's the documentation. I might be missing something but the error context didn't have the original data for me. https://developer.apple.com/documentation/swift/decodingerror

In order to not break the API, could we do something like wrap only if we detect a DecodingError into your custom error?

public enum REDecodingError: Error {
    case error(DecodingError, Data?)
}
    private func makeCall<T: Deserializer>(_ relativePath: String?, httpMethod: String, payload: Data?, responseDeserializer: T, options: RestOptions, callback: @escaping (Result<T.ResponseType>, HTTPURLResponse?) -> ()) {
        do {
            try dataTask(relativePath: relativePath, httpMethod: httpMethod, accept: responseDeserializer.acceptHeader, payload: payload, options: options) { (result, httpResponse) -> () in
                do {
                    let data = try result.value()
                    let transformedResponse = try responseDeserializer.deserialize(data)
                    callback(.success(transformedResponse), httpResponse)
                } catch let decodingError as  DecodingError {
                    callback(.failure(REDecodingError.error(decodingError, data)), httpResponse)
                } catch {
                    callback(.failure(error), httpResponse)
                }
            }
        } catch {
            callback(.failure(error), nil)
        }
    }

I can work around this using Postman to reproduce result on the iOS client. I just thought it would helpful figure out what exactly was returned from the response that failed the decode.

sean7512 commented 4 years ago

Ya, we could do that, I didn't suggest it earlier because by catching a specific type of deserializer error, it is now tightly coupled to the deserializer protocol. You could imagine this getting messy with a bunch of other catches for the other built-in serializers.

But now that I think of it, I can push this wrapper inside the deserializer itself and not in the RestController. I can try to get some time to work on this tomorrow, but I may not be able to. I'll let you know...thanks for the suggestion.

sean7512 commented 4 years ago

@yulius-fxpal RestEssentials 5.1 has been released. The DecodingError is now wrapped in the NetworkingError.malformedResponse, which includes the original, raw data, along with the DecodingError. Please let me know if that works.