Skip to content

Instantly share code, notes, and snippets.

@garvankeeley
Last active February 27, 2024 20:12
Show Gist options
  • Save garvankeeley/4e704e4af408682d02c3293aee497531 to your computer and use it in GitHub Desktop.
Save garvankeeley/4e704e4af408682d02c3293aee497531 to your computer and use it in GitHub Desktop.
import Foundation
import Network
import Combine
struct AuthParams: Encodable {
let staticParam1 = "fixedparam"
let staticParam2 = "anotherfixedparam"
let username: String
let password: String
}
protocol APIResponse {}
struct AuthResponse: Decodable, APIResponse {
let result: String
}
enum APIRequest {
case auth(String, String)
var httpMethod: String {
switch self {
case .auth: return "POST"
}
}
var endpoint: String {
switch self {
case .auth: return "/the/auth/endpoint"
}
}
var contentType: String {
switch self {
case .auth:
return "application/x-www-form-urlencoded"
}
}
var params: Encodable {
switch self {
case .auth(let username, let password):
return AuthParams(username: username, password: password)
}
}
func decode(jsonData: Data) throws -> APIResponse {
switch self {
case .auth:
return try JSONDecoder().decode(AuthResponse.self, from: jsonData)
}
}
func setHttpHeaders(for request: inout URLRequest) {
if self.httpMethod == "POST" {
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
}
}
}
class HTTPRequestService {
private let baseURL = "https://example.com"
private var lock = NSLock() // guard the cancellables
private var cancellables = Set<AnyCancellable>()
func request(_ apiRequest: APIRequest,
retryCount: Int,
completion: @escaping (Result<APIResponse, Error>) -> ()) {
var request = URLRequest(url: URL(string: baseURL + apiRequest.endpoint)!)
request.httpMethod = apiRequest.httpMethod
do {
let jsonBody = try JSONEncoder().encode(apiRequest.params)
request.httpBody = jsonBody
} catch {
// todo handle erros
}
apiRequest.setHttpHeaders(for: &request)
lock.withLock {
URLSession.shared.dataTaskPublisher(for: request)
.retry(retryCount)
.tryMap { result in
guard let response = result.response as? HTTPURLResponse,
(200..<300).contains(response.statusCode) else {
throw URLError(.badServerResponse)
}
return try apiRequest.decode(jsonData: result.data)
// NOTE: combine .decode(type: ..., decoder: ...)
// needs explicit type, won't work.
// Instead, make apiRequest.decode polymorphic.
}
.eraseToAnyPublisher()
.sink { result in
if case .failure(let error) = result {
completion(.failure(error))
}
} receiveValue: { apiResponse in
completion(.success(apiResponse)) // completion handler placeholder
}.store(in: &cancellables)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment