URLSession.DataTaskPublisher’s failure type

In March 2018, I wrote a piece titled Making illegal states unrepresentable, in which I argue that the “correct” result type for URLSession completion handlers is Result<(Data, URLResponse), (Error, URLResponse?)>.

In other words:

  • If the network requests succeeds, you receive (Data, URLResponse) — the response body as Data and a URLResponse representing the response metadata.
  • If the request fails, you receive (Error, URLResponse?) — an error and an optional URLResponse, because the request may have failed after receiving the initial response from the server.

Now, I don’t expect Apple to change the types of all completion handlers in their frameworks to use Result in Swift (although it would be cool and they did this before).

URLSession.DataTaskPublisher

But the introduction of the Combine framework gave Apple a chance to revisit the result type of a URLSession data task in a Swift-only context. Here’s what they chose for URLSession.DataTaskPublisher:

By throwing away some error information that most apps would probably never use, Apple did the pragmatic thing. In fact, they couldn’t have used an (Error, URLResponse?) tuple as a failure type because a publisher’s failure type must conform to the Error protocol and tuples can’t do that (for the same reason, the result type I proposed wouldn’t work as-is with the Result type in the standard library).

Alternatives

What they could have done is either define a new struct that wraps a URLError and a URLResponse?, or add a field to URLError for the optional response:

struct URLError {
  ...
  /// The response received from the server if we received
  /// a complete response before the error occurred.
  var response: URLResponse?
}

I like this second option because it’s also lightweight and pragmatic, at least from the user’s perspective. But I’m not sure how easy it would have been to implement given that URLError is a really just a façade for an NSError that’s bridged from Objective-C. I guess if the error handling code inside URLSession added the response object to the NSError’s userInfo dictionary, a computed property would suffice on the URLError side to extract it, but 🤷‍♂️.