Say I'm making an API call like this:
Task {
do {
let request: Request<LoginResponse> = .postLogin(email: email, password: password)
let response = try await URLSession.shared.decode(request)
// store token if received
} catch {
// ???
}
}
struct LoginResponse: Decodable {
let token: String
let msg: String
}
extension URLSession {
func decode<Value: Decodable>(
_ request: Request<Value>,
using decoder: JSONDecoder = .init()
) async throws -> Value {
let decoded = Task.detached(priority: .userInitiated) {
let (data, _) = try await self.data(for: request.urlRequest)
try Task.checkCancellation()
return try decoder.decode(Value.self, from: data)
}
return try await decoded.value
}
}
In the catch
block of the Task
, how would I access the "msg" from LoginResponse
? It's an error message being returned from the backend that the login info is incorrect. Or how would I restructure the do/catch so that I can access the LoginResponse
object in the catch block?
For further info on the Request
object, see here
If your backend still gives you an HTTP response if the login failed, then try await self.data(for: request.urlRequest)
won't throw an error. It will return a HTTPURLResponse
(which you completely ignored with _
) with a statusCode
indicating an error.
self.data(for: request.urlRequest)
would only throw when there is no response at all, like when you used an invalid URL, or there is no internet connection.
Therefore, you can return the HTTPURLResponse
back to the caller, and check it in the do
block:
func decode<Value: Decodable>(
_ request: Request<Value>,
using decoder: JSONDecoder = .init()
) async throws -> (Value, HTTPUURLResponse?) {
let decoded = Task.detached(priority: .userInitiated) {
let (data, response) = try await self.data(for: request.urlRequest)
try Task.checkCancellation()
return try (
decoder.decode(Value.self, from: data),
response as? HTTPURLResponse // this cast will succeed if the request is an HTTP request
)
}
return try await decoded.value
}
let request: Request<LoginResponse> = .postLogin(email: email, password: password)
let (decodedData, response) = try await URLSession.shared.decode(request)
if let httpResponse = response, httpResponse.statusCode >= 400 {
// handle error with decodedData...
}
If you want to handle it in the catch
block instead, you can check this in the task, and throw an error instead.
func decode<Value: Decodable, Failure: Error & Decodable>(
_ request: Request<Value>,
using decoder: JSONDecoder = .init(),
errorResponseType: Failure.Type
) async throws -> Value {
let decoded = Task.detached(priority: .userInitiated) {
let (data, response) = try await self.data(for: request.urlRequest)
try Task.checkCancellation()
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode >= 400 {
throw try decoder.decode(errorResponseType, from: data)
}
return try decoder.decode(Value.self, from: data)
}
return try await decoded.value
}
// these could be the same type, but I prefer separating errors from successful responses
struct LoginResponse: Decodable {
let token: String
let msg: String
}
struct LoginError: Decodable, Error {
let token: String
let msg: String
}
do {
let request: Request<LoginResponse> = .postLogin(email: email, password: password)
let decodedData = try await URLSession.shared.decode(request, errorResponseType: LoginError.self)
} catch let error as LoginError {
// handle login error...
} catch {
// handle other errors...
}