Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save yujinqiu/7b75435730c4479759552a1caabc4281 to your computer and use it in GitHub Desktop.
Save yujinqiu/7b75435730c4479759552a1caabc4281 to your computer and use it in GitHub Desktop.
Combine - fetching and decoding JSON data
import Foundation
import Combine
enum APIError: Error, LocalizedError {
case unknown, apiError(reason: String), parserError(reason: String)
var errorDescription: String? {
switch self {
case .unknown:
return "Unknown error"
case .apiError(let reason), .parserError(let reason):
return reason
}
}
}
struct Fact: Decodable {
var text: String
}
func fetch(url: URL) -> AnyPublisher<Data, APIError> {
let request = URLRequest(url: url)
return URLSession.DataTaskPublisher(request: request, session: .shared)
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
throw APIError.unknown
}
return data
}
.mapError { error in
if let error = error as? APIError {
return error
} else {
return APIError.apiError(reason: error.localizedDescription)
}
}
.eraseToAnyPublisher()
}
func fetch<T: Decodable>(url: URL) -> AnyPublisher<T, APIError> {
fetch(url: url)
.decode(type: T.self, decoder: JSONDecoder())
.mapError { error in
if let error = error as? DecodingError {
var errorToReport = error.localizedDescription
switch error {
case .dataCorrupted(let context):
let details = context.underlyingError?.localizedDescription ?? context.codingPath.map { $0.stringValue }.joined(separator: ".")
errorToReport = "\(context.debugDescription) - (\(details))"
case .keyNotFound(let key, let context):
let details = context.underlyingError?.localizedDescription ?? context.codingPath.map { $0.stringValue }.joined(separator: ".")
errorToReport = "\(context.debugDescription) (key: \(key), \(details))"
case .typeMismatch(let type, let context), .valueNotFound(let type, let context):
let details = context.underlyingError?.localizedDescription ?? context.codingPath.map { $0.stringValue }.joined(separator: ".")
errorToReport = "\(context.debugDescription) (type: \(type), \(details))"
@unknown default:
break
}
return APIError.parserError(reason: errorToReport)
} else {
return APIError.apiError(reason: error.localizedDescription)
}
}
.eraseToAnyPublisher()
}
// Usage
if let url = URL(string: "https://cat-fact.herokuapp.com/facts/random") {
fetch(url: url)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}, receiveValue: { (fact: Fact) in
print(fact.text)
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment