import Foundation
public final class Api {
public struct NoContent: Decodable { init() {} } public enum ApiError: Error { case nilDataError case pathError case repeatCountLimitReached } enum Method: String { case get = "GET" case post = "POST" case put = "PUT" } private let session = URLSession(configuration: URLSessionConfiguration.default) private let baseURL: URL public init(baseURL: String) { self.baseURL = URL(string: baseURL)! } public func get<T: Codable>( path: String, query: [String: String] = [:], headers: [String: String] = [:], timeoutInterval: TimeInterval ) throws -> T { return try self.method(.get, path: path, query: query, headers: headers, timeoutInterval: timeoutInterval) } public func post<T: Codable, TBody: Encodable>( path: String, body: TBody, query: [String: String] = [:], headers: [String: String] = [:], timeoutInterval: TimeInterval ) throws -> T { let body = try JSONEncoder().encode(body) return try self.method(.post, path: path, query: query, headers: headers, timeoutInterval: timeoutInterval, body: body) } private func get<T: Codable>(path: String, completion: @escaping (Result<T, Error>) -> Void) { self.method(.get, path: path, completion: completion) } private func post<T: Codable, TBody: Encodable>( path: String, body: TBody, headers: [String: String] = [:], completion: @escaping (Result<T, Error>) -> Void ) { let body = try? JSONEncoder().encode(body) self.method(.post, path: path, headers: headers, body: body, completion: completion) } private func method<T: Decodable>( _ method: Method, path: String, query: [String: String] = [:], headers: [String: String] = [:], timeoutInterval: TimeInterval, body: Data? = nil ) throws -> T { let s = DispatchSemaphore(value: 0) var statsResponse: Result<T, Error>! let completion: (Result<T, Error>) -> Void = { result in statsResponse = result s.signal() } self.method( method, path: path, query: query, headers: headers, body: body, timeoutInterval: timeoutInterval, completion: completion ) s.wait() return try statsResponse.get() } private func method<T: Decodable>( _ method: Method, path: String, query: [String: String] = [:], headers: [String: String] = [:], body: Data? = nil, timeoutInterval: TimeInterval = 60, completion: @escaping (Result<T, Error>) -> Void ) { let baseURL = self.baseURL.appendingPathComponent(path) var cmp = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) cmp?.queryItems = query.map { URLQueryItem(name: $0.key, value: $0.value) } guard let url = cmp?.url else { completion(.failure(ApiError.pathError)); return } var request = URLRequest(url: url) request.timeoutInterval = timeoutInterval request.httpBody = body request.httpMethod = method.rawValue var allHTTPHeaderFields = headers if headers["content-type"] == nil { allHTTPHeaderFields["content-type"] = "application/json" } request.allHTTPHeaderFields = allHTTPHeaderFields print("Start request: \(method.rawValue) \(url)") self.session.dataTask(with: request) { (data, _, error) in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(ApiError.nilDataError)) return } if data.isEmpty, let noContent = NoContent() as? T { completion(.success(noContent)) return } if let string = String(data: data, encoding: .utf8)?.prefix(3000) { print("Finish: \(string)...") } else { print("Finish") } do { let response = try JSONDecoder().decode(T.self, from: data) completion(.success(response)) } catch { print("Decode response \(url) error: \(error)") completion(.failure(error)) } }.resume() }
}