Swift合并以MapURLSession.shared.dataTaskPublisher HTTP响应错误

fcwjkofz  于 2022-11-21  发布在  Swift
关注(0)|答案(3)|浏览(146)

给定一个API,对于无效的请求,服务器返回一个JSON有效载荷,其中包含一个可读的消息。例如,对于已删除或不存在的内容,服务器可以返回带有404状态代码的{ "message": "Not Found" }
如果不使用发布程序,代码将显示为,

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

func request(request: URLRequest) async throws -> Post {
  let (data, response) = try await URLSession.shared.data(for: request)

  let statusCode = (response as! HTTPURLResponse).statusCode
  if 400..<500 ~= statusCode {
    throw try JSONDecoder().decode(APIErrorResponse.self, from: data)
  }

  return try JSONDecoder().decode(Post.self, from: data)
}

换句话说,如何根据HTTPURLResponse.statusCode属性对不同的类型进行解码,以返回错误,或者更一般地说,如何将response属性与data属性分开处理?

URLSession.shared.dataTaskPublisher(for: request)
  .map(\.data)
  .decode(type: Post.self, decoder: JSONDecoder())
  .eraseToAnyPublisher()
py49o6xq

py49o6xq1#

您可以尝试类似以下方法:

func request(request: URLRequest) -> AnyPublisher<Post, any Error> {
    URLSession.shared.dataTaskPublisher(for: request)
        .tryMap { (output) -> Data in
            let statusCode = (output.response as! HTTPURLResponse).statusCode
            if 400..<500 ~= statusCode {
                throw try JSONDecoder().decode(APIErrorResponse.self, from: output.data)
            }
            return output.data
        }
        .decode(type: Post.self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}
2lpgd968

2lpgd9682#

我使用了一个helper方法:

extension Publisher where Output == (data: Data, response: HTTPURLResponse) {

    func decode<Success, Failure>(
        success: Success.Type,
        failure: Failure.Type,
        decoder: JSONDecoder
    ) -> AnyPublisher<Success, Error> where Success: Decodable, Failure: DecodableError {
        tryMap { data, httpResponse -> Success in
            guard httpResponse.statusCode < 500 else {
                throw MyCustomError.serverUnavailable(status: httpResponse.statusCode)
            }
            guard httpResponse.statusCode < 400 else {
                let error = try decoder.decode(failure, from: data)
                throw error
            }
            let success = try decoder.decode(success, from: data)

            return success
        }
        .eraseToAnyPublisher()
    }
}

typealias DecodableError = Decodable & Error

这样我就可以简化调用点了:

URLSession.shared.dataTaskPublisher(for: request)
  .decode(success: Post.self, failure: MyCustomError.self, decoder: JSONDecoder())
  .eraseToAnyPublisher()
mzillmmw

mzillmmw3#

workingdogsupport提供了一个很好的直译(+1)。LuLuGaGa说明了一个很好的构图风格(+1)。
不过,我可能会对后者进行扩展,并建议对各种状态代码进行模式匹配,例如,2xx代码表示解码成功,4xx代码表示正常的Web服务错误,而更通用的.badServerResponse(包括诊断信息,以便在调用点上工作的开发人员有机会找出错误所在)表示其他任何代码。
例如,您可能有一个通用扩展(不使用任何特定于应用的类型):

extension Publisher where Output == (data: Data, response: URLResponse) {
    func decode<Success: Decodable, Failure: Decodable & Error>(
        success: Success.Type = Success.self,
        failure: Failure.Type = Failure.self,
        decoder: JSONDecoder = JSONDecoder()
    ) -> AnyPublisher<Success, Error> {
        tryMap { data, response -> Success in
            switch (response as! HTTPURLResponse).statusCode {
            case 200..<300: return try decoder.decode(Success.self, from: data)
            case 400..<500: throw try decoder.decode(Failure.self, from: data)
            default:        throw URLError(.badServerResponse, userInfo: ["data": data, "response": response])
            }
        }
        .eraseToAnyPublisher()
    }
}

或者,因为我讨厌强制解包:

extension Publisher where Output == (data: Data, response: URLResponse) {
    func decode<Success: Decodable, Failure: Decodable & Error>(
        success: Success.Type = Success.self,
        failure: Failure.Type = Failure.self,
        decoder: JSONDecoder = JSONDecoder()
    ) -> AnyPublisher<Success, Error> {
        tryMap { data, response -> Success in
            guard let response = response as? HTTPURLResponse else {
                throw URLError(.badServerResponse, userInfo: ["data": data, "response": response])
            }
            
            switch response.statusCode {
            case 200..<300: return try decoder.decode(Success.self, from: data)
            case 400..<500: throw try decoder.decode(Failure.self, from: data)
            default:        throw URLError(.badServerResponse, userInfo: ["data": data, "response": response])
            }
        }
        .eraseToAnyPublisher()
    }
}

无论如何,我可能会为这个应用程序提供一个扩展,用于解码特定Web服务的特定错误struct

extension Publisher where Output == (data: Data, response: URLResponse) {
    func decode<Success: Decodable>(
        success: Success.Type = Success.self,
        decoder: JSONDecoder = JSONDecoder()
    ) -> AnyPublisher<Success, Error> {
        decode(success: success, failure: APIErrorResponse.self, decoder: decoder)
    }
}

然后,应用程序代码可以利用上述内容(并推断出success类型):

func postsPublisher(for request: URLRequest) -> AnyPublisher<Post, Error> {
    URLSession.shared.dataTaskPublisher(for: request)
        .decode()
        .eraseToAnyPublisher()
}

无论如何,这会产生一个简洁的调用点,并带有一个可重用的扩展。

相关问题