I have a publisher wrapper struct where I can handle response status code. If the status code is not range in 200..300 it return with an object, otherwise it throws an Error. It works well.
public func anyPublisher<T:Decodable>(type: T.Type) -> AnyPublisher<T, Error> {
return URLSession.shared.dataTaskPublisher(for: urlRequest)
.tryMap { output in
guard let httpResponse = output.response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
throw APIError.unknown
}
return output.data
}
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
Using:
let sendNewUserPublisher = NetworkPublisher(urlRequest: request).anyPublisher(type: User.self)
cancellationToken = sendNewUserPublisher.sink(receiveCompletion: { completion in
if case let .failure(error) = completion {
NSLog("error: \(error.localizedDescription)")
}
}, receiveValue: { post in
self.post = post
})
As above, I would like to handle the error even if the response data does not contain an object to be decoded.
public func anyPublisher() -> AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure> {
return URLSession.shared.dataTaskPublisher(for: urlRequest)
// I'd like to handle status code here, and throw an error, if needed
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
Thank you in advance for any help you can provide.
I would suggest creating a Publisher
that handles the HTTP response status code validation and using that for both of your other publishers - the one that handles an empty request body and the one that decodes the body.
If you need the HTTPURLResponse
object even after validating its status code:
extension URLSession.DataTaskPublisher {
/// Publisher that throws an error in case the data task finished with an invalid status code, otherwise it simply returns the body and response of the HTTP request
func httpResponseValidator() -> AnyPublisher<Output, CustomError> {
tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse else { throw CustomError.nonHTTPResponse }
let statusCode = httpResponse.statusCode
guard (200..<300).contains(statusCode) else { throw CustomError.incorrectStatusCode(statusCode) }
return (data, httpResponse)
}
.mapError { CustomError.network($0) }
.eraseToAnyPublisher()
}
}
Or if you don't care about any other properties of the response, only that its status code was valid:
func httpResponseValidator() -> AnyPublisher<Data, CustomError> {
tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse else { throw CustomError.nonHTTPResponse }
let statusCode = httpResponse.statusCode
guard (200..<300).contains(statusCode) else { throw CustomError.incorrectStatusCode(statusCode) }
return data
}
.mapError { CustomError.network($0) }
.eraseToAnyPublisher()
}
Then you can use this to rewrite both versions of your anyPublisher
function:
extension URLSession.DataTaskPublisher {
func anyPublisher<T:Decodable>(type: T.Type) -> AnyPublisher<T, Error> {
httpResponseValidator()
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
func anyPublisher() -> AnyPublisher<Output, CustomError> {
httpResponseValidator()
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}