AsMartynas
AsMartynas

Reputation: 167

Combine handle different type of publishers

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

Answers (1)

David Pasztor
David Pasztor

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 switching 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

Related Questions