如何解码 Combine 中的错误响应消息?

How to decode error response message in Combine?

我正在使用 SwiftUI 和 Combine 进行登录。您能否告诉我一些想法,当用户输入错误的电子邮件或密码时,我该如何解码并显示 json 错误?我只能得到token

当我使用不正确的电子邮件或密码执行相同的登录请求时,服务器 returns 我收到此错误消息:

{
"code": "[jwt_auth] incorrect_password",
"message": "Incorrect password!",
"data": {
    "status": 403
}

}

问题是我无法理解在 Combine 中执行一个请求时如何解码两个不同的 json 响应?我只能得到token

这是登录请求的模型:

struct LoginResponse: Decodable {
let token: String }

struct ErrorResponse: Decodable {
    let message: String
}
struct Login: Codable {
    let username: String
    let password: String
}

static func login(email: String, password: String) -> AnyPublisher<LoginResponse, Error> {
    let url = MarketplaceAPI.jwtAuth!
    var request = URLRequest(url: url)

    let encoder = JSONEncoder()
    let login = Login(username: email, password: password)
    let jsonData = try? encoder.encode(login)
    
    request.httpBody = jsonData
    request.httpMethod = HTTPMethod.POST.rawValue
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    
    return URLSession.shared
        .dataTaskPublisher(for: request)
        .print()
        .receive(on: DispatchQueue.main)
        .map(\.data)
        .decode(
          type: LoginResponse.self,
          decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

并且在视图模型中:

MarketplaceAPI.login(email: email, password: password)
        .sink(
          receiveCompletion: { completion in
              switch completion {
              case .finished:
                  print("finished")
              case .failure(let error):
                  print("Failure error:", error.localizedDescription) // This's returning token error 
              }
          },
          receiveValue: { value in
              print("Token:", value.token)
             }
          })
        .store(in: &subscriptions)
}

您可以结合使用 tryMap 和 combine 来找出函数应该 return 的位置。我建议您阅读有关它的文档,但这里有一个片段应该可以让您继续使用它。

希望这就是您提出问题的意思 - 我已经更改了一些内容,但您可以随意将代码作为构建块并根据需要进行调整!

enum LoginError: Error, Equatable {
    case noConnection
    case invalidResponse
}

static func login(email: String, password: String) -> AnyPublisher<Void, LoginError> {
    return URLSession.shared
        .dataTaskPublisher(for: request)
        .print()
        .receive(on: DispatchQueue.main)
        .mapError { _ in LoginError.noConnection }
        .tryMap { (data, response) in
            guard let response = response as? HTTPURLResponse else {
                throw LoginError.invalidResponse
            }
                
            if response.statusCode == 200 {
                return data
            } else {
                throw LoginError.invalidResponse
            }
        }
        .decode(type: LoginResponse.self, decoder: JSONDecoder())
        .tryMap { [unowned self] in

            // Update your session with the token here using [=10=] then return. 
            // e.g session.token = [=10=].token

            return
        }
        .mapError { [=10=] as? LoginError ?? .invalidResponse }
        .eraseToAnyPublisher()
}

我会让 ErrorResponse 符合 Error 协议:

struct ErrorResponse: Decodable, Error {
    let message: String
}

然后,使用 tryMap 而不是 decode(这是 tryMap 的一种特殊情况)。

.tryMap({ data -> LoginResponse in
    let decoder = JSONDecoder()
    guard let loginResponse = try? decoder.decode(LoginResponse.self, from: data) else {
        throw try decoder.decode(ErrorResponse.self, from: data)
    }
    return loginResponse
})

首先,尝试将数据解码为 LoginResponse。注意这里使用 try?。这样我们就可以检查它是否失败了。如果失败,我们将抛出一个错误。我们抛出的错误是解码为 ErrorResponse 的数据,或者在解码期间抛出的任何错误。

在您的视图模型中,您可以这样检查错误:

.sink { completion in
    switch completion {
    case .failure(let error as ErrorResponse):
        // wrong password/username
        // you can access error.message here
    case .failure(let error):
        // some other sort of error:
    default:
        break
    }
} receiveValue: { loginResponse in
    ...
}