Last active
December 8, 2023 17:33
-
-
Save umurgdk/1f3b17b63b44108cf0442ae2db33b394 to your computer and use it in GitHub Desktop.
Poor man's network client
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 Cocoa | |
enum HTTPMethod: String { | |
case get | |
case put | |
case post | |
case delete | |
} | |
protocol Request { | |
associatedtype Response | |
var method: HTTPMethod { get } | |
func url(base: URL?) -> URL | |
func body(encoder: JSONEncoder) throws -> Data? | |
func decodeResponse(_ response: HTTPURLResponse, data: Data, decoder: JSONDecoder) throws -> Response | |
} | |
extension Request { | |
func body(encoder: JSONEncoder) throws -> Data? { | |
return nil | |
} | |
} | |
extension Request where Response: Decodable { | |
func decodeResponse(_ response: HTTPURLResponse, data: Data, decoder: JSONDecoder) throws -> Response { | |
try decoder.decode(Response.self, from: data) | |
} | |
} | |
extension Request where Response == Void { | |
func decodeResponse(_ response: HTTPURLResponse, data: Data, decoder: JSONDecoder) throws -> Response { | |
return () | |
} | |
} | |
protocol NetworkClient { | |
func request<R: Request>(_ request: R) async throws -> R.Response | |
} | |
struct Network: NetworkClient { | |
let session: URLSession | |
let baseURL: URL? | |
let decoder: JSONDecoder | |
let encoder: JSONEncoder | |
init( | |
session: URLSession = .shared, | |
baseURL: URL?, | |
decoder: JSONDecoder = JSONDecoder(), | |
encoder: JSONEncoder = JSONEncoder() | |
) { | |
self.session = session | |
self.baseURL = baseURL | |
self.decoder = decoder | |
self.encoder = encoder | |
} | |
func request<R: Request>(_ request: R) async throws -> R.Response { | |
let urlRequest = try makeURLRequest(from: request) | |
let (data, urlResponse) = try await session.data(for: urlRequest) | |
// TODO: check response status code | |
guard let httpResponse = urlResponse as? HTTPURLResponse else { | |
throw URLError(.unknown) | |
} | |
return try request.decodeResponse(httpResponse, data: data, decoder: decoder) | |
} | |
private func makeURLRequest<R: Request>(from request: R) throws -> URLRequest { | |
let url = request.url(base: baseURL) | |
var urlRequest = URLRequest(url: url) | |
urlRequest.httpMethod = request.method.rawValue | |
urlRequest.httpBody = try request.body(encoder: encoder) | |
return urlRequest | |
} | |
} | |
// MARK: - Example Usage | |
struct Todo: Decodable { | |
let label: String | |
let isDone: Bool | |
} | |
struct GetTodos: Request { | |
typealias Response = [Todo] | |
let limit: Int? | |
let method = HTTPMethod.get | |
func url(base: URL?) -> URL { | |
var components = URLComponents(string: "/todos", encodingInvalidCharacters: true) | |
if let limit { | |
components?.queryItems = [URLQueryItem(name: "limit", value: String(limit))] | |
} | |
return components!.url(relativeTo: base)! | |
} | |
} | |
let network = Network(baseURL: URL(string: "https://api.myservice.com")) | |
let todos = try await network.request(GetTodos(limit: 1)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For empty responses: