Комбинируйте ручки разных типов издателей

Я действительно новичок в Combine, и я застрял с этой проблемой. У меня есть базовая форма регистрации, которая возвращает пустой ответ с кодом 200, если все в порядке, и 442, если в форме есть ошибки регистрации.

Это код, который может обрабатывать пустой ответ и отлично работает

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()
    }
}

Однако как я мог вернуть издателя с другим типом? Например

struct CustomError: Decodable {
    let usernameError: String
    let emailError: String
}

Мой сетевой вызов:

    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)

person AsMartynas    schedule 03.11.2020    source источник
comment
Так всегда ли ваш сетевой запрос возвращает CustomError в качестве ответа?   -  person Dávid Pásztor    schedule 03.11.2020
comment
Не всегда, только если есть проблемы с проверкой формы. Если нет проблем с проверкой формы, я получу пустой ответ с кодом 200   -  person AsMartynas    schedule 03.11.2020


Ответы (1)


Итак, у вас есть сетевой запрос, который в случае успешного запроса возвращает ответ 200 и пустое тело, а в случае ошибки формы он возвращает конкретный код состояния и ошибку в ответе.

Я бы посоветовал оставить Output тип вашего Publisher как Void, однако, в случае ошибки формы, расшифровать ошибку и выбросить ее как часть вашего 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()
}

Затем вы можете обработать конкретную ошибку, switch переместив error в 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)
person Dávid Pásztor    schedule 03.11.2020
comment
Удивительный! Однако возникает еще один вопрос. В моей ViewModel я вижу, что выдается только сетевая ошибка, а не та, которую мы проанализировали, когда код состояния был 442. Может быть .mapError переопределяет выданную ранее ошибку? - person AsMartynas; 03.11.2020
comment
@AsMartyn Хорошо, если ваш фактический сетевой запрос не выполняется, то да, dataTaskPublisher завершится ошибкой и, следовательно, будет вызываться mapError, а не tryMap. Однако dataTaskPublisher сбой с ошибкой означает, что на запрос не поступил ответ, поскольку сам запрос завершился ошибкой. Вам необходимо выяснить, почему ваш запрос не выполняется. - person Dávid Pásztor; 03.11.2020
comment
Это странно, потому что я могу распечатать данные в коде состояния, если оператор и AuthError успешно декодированы, поэтому кажется, что mapError все равно вызывается. Из логов вижу ПУСТОЙ РЕЗУЛЬТАТ: прием отмены, может в чем дело? - person AsMartynas; 03.11.2020
comment
@AsMartynas, на самом деле, плохо, если вы выдадите ошибку в tryMap, эта ошибка будет распространена на mapError и не сразу же подведет издателя. Проверьте мой обновленный ответ, теперь mapError должен правильно обрабатывать ошибки, возникающие внутри tryMap. - person Dávid Pásztor; 03.11.2020
comment
Большое спасибо. Если не возражаете, есть еще один вопрос. В моей ViewModel я вызываю свой сетевой запрос и с помощью .sink (receiveOnCompletion, receiveValue) пытаюсь обработать ошибки. Как я мог привести это к моей ошибке входа в систему? Я обновил свой код выше - person AsMartynas; 03.11.2020
comment
@AsMartynas, вам просто нужно switch над error в sink. Проверьте мой обновленный ответ. - person Dávid Pásztor; 03.11.2020