Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift combine handling HTTP status code errors

Tags:

swift

combine

I was reading this article: https://www.raywenderlich.com/4161005-mvvm-with-combine-tutorial-for-ios on Ray Wenderlich about how to use combine. They have an example where it fetches data from an API but it doesn't handle HTTP status codes. I wanted to add it but so far I'm not able to do so.

According to this answer you could add a tryMap but then XCode starts showing errors like: Generic parameter 'T' could not be inferred.

Below the code:

extension WeatherFetcher: WeatherFetchable {
  func weeklyWeatherForecast(
    forCity city: String
  ) -> AnyPublisher<WeeklyForecastResponse, WeatherError> {
    return forecast(with: makeWeeklyForecastComponents(withCity: city))
  }

  private func forecast<T>(
    with components: URLComponents
  ) -> AnyPublisher<T, WeatherError> where T: Decodable {
    guard let url = components.url else {
      let error = WeatherError.network(description: "Couldn't create URL")
      return Fail(error: error).eraseToAnyPublisher()
    }
    return session.dataTaskPublisher(for: URLRequest(url: url))
      .mapError { error in
        .network(description: error.localizedDescription)
    }
    .flatMap(maxPublishers: .max(1)) { pair in
      decode(pair.data)
    }
    .eraseToAnyPublisher()
  }
}

And I was trying to add

.tryMap { data, response in
    guard let httpResponse = response as? HTTPURLResponse,
        200..<300 ~= httpResponse.statusCode else {
            switch (response as! HTTPURLResponse).statusCode {
            case (400...499):
                throw ServiceErrors.internalError((response as! HTTPURLResponse).statusCode)
            default:
                throw ServiceErrors.serverError((response as! HTTPURLResponse).statusCode)
            }
    }
    return data
}
like image 650
Bart Avatar asked May 17 '26 15:05

Bart


1 Answers

I think you can simply replace the flatMap block with tryMap. And instead of returning data from tryMap, it should be decoded T. So return data line should be return try JSONDecoder().decode(T.self, from: data)

private func forecast<T>(with components: URLComponents) -> AnyPublisher<T, WeatherError> where T: Decodable {
guard let url = components.url else {
    let error = WeatherError.network(description: "Couldn't create URL")
    return Fail(error: error).eraseToAnyPublisher()
}
return session.dataTaskPublisher(for: URLRequest(url: url))
    .tryMap { data, response in
        guard let httpResponse = response as? HTTPURLResponse,
            200..<300 ~= httpResponse.statusCode else {
                switch (response as! HTTPURLResponse).statusCode {
                case (400...499):
            throw ServiceErrors.internalError((response as! HTTPURLResponse).statusCode)
                default:
                    throw ServiceErrors.serverError((response as! HTTPURLResponse).statusCode)
                }
        }
        return try JSONDecoder().decode(T.self, from: data)
}
.mapError { error in
    WeatherError() // some kind of error
}
.eraseToAnyPublisher()

}

like image 147
noob Avatar answered May 19 '26 04:05

noob