Browse Questions

Answer: Define a clear error type, validate HTTP status codes, and propagate errors up to the UI where they can be shown to the user.

Code Example:

enum APIError: LocalizedError {
    case badURL
    case unauthorized
    case notFound
    case serverError(Int)
    case decodingFailed(Error)
    case unknown

    var errorDescription: String? {
        switch self {
        case .unauthorized:     return "Please log in again."
        case .notFound:         return "Resource not found."
        case .serverError(let code): return "Server error (\(code))."
        default:                return "Something went wrong."
        }
    }
}

func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let http = response as? HTTPURLResponse else { throw APIError.unknown }

    switch http.statusCode {
    case 200...299: break
    case 401: throw APIError.unauthorized
    case 404: throw APIError.notFound
    default:  throw APIError.serverError(http.statusCode)
    }

    do {
        return try JSONDecoder().decode(T.self, from: data)
    } catch {
        throw APIError.decodingFailed(error)
    }
}