I'm working on an APIClient that gets JSON from the backend, also in cases of failures.
I'm handling parsing of the 'happy path' differently than service-wide errors.
Consider this:
import UIKit
var greeting = "Hello, playground"
protocol ErrorResponse: Codable {
var asErrors: [Error] { get }
}
class FakeClient {
var responseTypes: [ErrorResponse.Type] = []
func registerResponse(type: ErrorResponse.Type) {
responseTypes.append(type)
}
func parse(responseData: Data = Data()) throws {
let decoder = JSONDecoder()
for type in responseTypes {
if let response = try? decoder.decode(type, from: responseData) {
// SEE ERROR MESSAGE BELOW
// Got an error response
throw response.asErrors.first!
}
}
}
}
And that line "SEE ERROR MESSAGE BELOW" produces the compiler error of:
Cannot convert value of type 'ErrorResponse.Type' to expected argument type 'T.Type'
Generic parameter 'T' could not be inferred
I'm a little confused, perhaps because I'm storing an array of protocol types.
I don't know why the decoder can't determine its type, since ErrorResponse conforms to Codable.
I suppose I need to type erase somehow because it needs to understand what concrete type is actually in use?
Does anyone know the right way to accomplish this?
CodePudding user response:
The solution is to create an error response handler then type erase it. The handler itself determines what type it will want to try to decode, and if it can, it will throw that error. If not, it won't, and the code will use the next handler in the array to try to parse it. If none of them throw, there would be no special error parsed, and one can just fallback to a more generic HTTPError. (this code is used when the status code is 422, typically a validation error, but that's somewhat irrelevant here)
I'll let the code speak for itself:
protocol ErrorResponseHandler {
/// if this handler can parse it, it will throw, if not, nothing will happen.
func decodeData(_ responseData: Data, with decoder: JSONDecoder) throws
}
struct AnyErrorHandler: ErrorResponseHandler {
let wrapped: ErrorResponseHandler
init<T: ErrorResponseHandler>(_ handler: T) {
self.wrapped = handler
}
func decodeData(_ responseData: Data, with decoder: JSONDecoder) throws {
try self.wrapped.decodeData(responseData, with: decoder)
}
}
class FakeClient {
private var errorHandlers: [AnyErrorHandler] = []
func registerErrorResponseHandler<T: ErrorResponseHandler>(_ handler: T) {
errorHandlers.append(AnyErrorHandler(handler))
}
func parse(responseData: Data = Data()) throws {
let decoder = JSONDecoder()
for handler in errorHandlers {
try handler.decodeData(responseData, with: decoder)
}
}
}
// Now to set up some boilerplate to test this out.
enum FakeError: Error {
case justTestingThis
}
struct FakeHandler: ErrorResponseHandler {
func decodeData(_ responseData: Data, with decoder: JSONDecoder) throws {
throw FakeError.justTestingThis
}
}
let client = FakeClient()
client.registerErrorResponseHandler(FakeHandler())
do {
try client.parse(responseData: Data())
} catch let e {
print("I think it worked: \(String(describing: e))")
}