Created
October 13, 2022 11:37
-
-
Save Tarkhan99/925431a1c614fb0c62a33c71e2430ade to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Combine | |
protocol APIRequestType { | |
associatedtype ModelType: Decodable | |
var path: String {get} | |
var method: String {get} | |
var headers: [String: String]? {get} | |
var queryItems: [URLQueryItem]? {get} | |
func body() throws -> Data? | |
} | |
enum APIServiceError: Error { | |
case invalidURL | |
case httpError(HTTPCode) | |
case parseError | |
case unexpectedResponse | |
} | |
extension APIServiceError: LocalizedError { | |
var errorDescription: String? { | |
switch self { | |
case .invalidURL: return "Invalid URL" | |
case let .httpError(statusCode): return "Unexpected HTTP status code: \(statusCode)" | |
case .parseError: return "Unexpected JSON parse error" | |
case .unexpectedResponse: return "Unexpected response from the server" | |
} | |
} | |
} | |
typealias HTTPCode = Int | |
typealias HTTPCodes = Range<HTTPCode> | |
extension HTTPCodes { | |
static let success = 200 ..< 300 | |
} | |
extension APIRequestType { | |
func buildRequest(baseURL: String) throws -> URLRequest { | |
guard let url = URL(string: baseURL + path) else { | |
throw APIServiceError.invalidURL | |
} | |
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)! | |
components.queryItems = queryItems | |
var request = URLRequest(url: components.url!) | |
request.httpMethod = method | |
request.allHTTPHeaderFields = headers | |
request.httpBody = try body() | |
return request | |
} | |
} | |
struct Request: APIRequestType { | |
typealias ModelType = Response | |
var path: String | |
var method: String { return "GET" } | |
var headers: [String: String]? { return ["Content-Type": "application/json"] } | |
var queryItems: [URLQueryItem]? | |
func body() throws -> Data? { | |
return Data() | |
} | |
} | |
protocol APIServiceType { | |
var session: URLSession {get} | |
var baseURL: String {get} | |
var bgQueue: DispatchQueue {get} | |
func call<Request>(from endpoint: Request) -> AnyPublisher<Request.ModelType, Error> where Request: APIRequestType | |
} | |
final class APIService: APIServiceType { | |
internal let baseURL: String | |
internal let session: URLSession = URLSession.shared | |
internal let bgQueue: DispatchQueue = DispatchQueue.main | |
init(baseURL: String = "") { | |
self.baseURL = baseURL | |
} | |
func call<Request>(from endpoint: Request) -> AnyPublisher<Request.ModelType, Error> where Request : APIRequestType { | |
do { | |
let request = try endpoint.buildRequest(baseURL: baseURL) | |
return session.dataTaskPublisher(for: request) | |
.retry(1) | |
.tryMap { | |
guard let code = ($0.response as? HTTPURLResponse)?.statusCode else { | |
throw APIServiceError.unexpectedResponse | |
} | |
guard HTTPCodes.success.contains(code) else { | |
throw APIServiceError.httpError(code) | |
} | |
return $0.data | |
} | |
.decode(type: Request.ModelType.self, decoder: JSONDecoder()) | |
.mapError {_ in APIServiceError.parseError} | |
.receive(on: self.bgQueue) | |
.eraseToAnyPublisher() | |
} catch let error { | |
return Fail<Request.ModelType, Error>(error: error).eraseToAnyPublisher() | |
} | |
} | |
} | |
let apiService: APIServiceType = ApiService() | |
let request = Request(path: "/path") | |
let publisher = apiService.call(from: endpoint) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment