Reputation: 167
I am really new to Combine and I'm stuck with this issue. I have basic registration form, that returns empty response with 200 code if everything is ok and 442 if form has some registration failures.
That's the code that can handle empty response and works fine
extension Route where ResultType: EmptyResult {
func emptyResult() -> AnyPublisher<Void, APIError> {
return URLSession.shared.dataTaskPublisher(for: urlRequest)
.print("EMPTY RESULT")
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse else { throw APIError.nonHttpResponse(description: "Not http resp") }
let statusCode = httpResponse.statusCode
guard (200..<300).contains(statusCode) else { throw APIError.nonHttpResponse(description: "bad response")
}
return Void()
}.mapError { error in
print("Error \(error)")
return .network(description: error.localizedDescription)
}
.eraseToAnyPublisher()
}
}
However, how I could return publisher with other type? For example
struct CustomError: Decodable {
let usernameError: String
let emailError: String
}
My network call:
API.registration(name: name, email: email, password: password, schoolID: selectedSchool?.id ?? 0)
.print("Registration")
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (completion) in
switch completion {
case let .failure(error):
print("ERROR \(error)")
case .finished: break
}
}, receiveValue: { value in
print(value)
})
.store(in: &disposables)
Upvotes: 1
Views: 495
Reputation: 54775
So you have a network request, which in case of a successful request, returns a 200 response and an empty body, while in case of a form error, it returns a specific status code and an error in the response.
I would suggest keeping the Output
type of your Publisher
as Void
, however, in case of a form error, decoding the error and throwing it as part of your APIError
.
struct LoginError: Decodable {
let usernameError: String
let emailError: String
}
enum APIError: Error {
case failureStatus(code: Int)
case login(LoginError)
case nonHttpResponse(description: String)
case network(Error)
}
func emptyResult() -> AnyPublisher<Void, APIError> {
return URLSession.shared.dataTaskPublisher(for: urlRequest)
.print("EMPTY RESULT")
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse else { throw APIError.nonHttpResponse(description: "Not http response") }
let statusCode = httpResponse.statusCode
guard (200..<300).contains(statusCode) else {
if statusCode == 442 {
let loginError = try JSONDecoder().decode(LoginError.self, from: data)
throw APIError.login(loginError)
} else {
throw APIError.failureStatus(code: statusCode)
}
}
return Void()
}.mapError { error in
switch error {
case let apiError as APIError:
return apiError
default:
return .network(error)
}
}
.eraseToAnyPublisher()
}
Then you can handle the specific error by switch
ing over the error
in sink
:
API.registration(name: name, email: email, password: password, schoolID: selectedSchool?.id ?? 0)
.print("Registration")
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (completion) in
switch completion {
case let .failure(error):
switch error {
case .login(let loginError):
print("Login failed, \(loginError.emailError), \(loginError.usernameError)")
default:
print(error)
}
case .finished: break
}
}, receiveValue: { value in
print(value)
})
.store(in: &disposables)
Upvotes: 1