Files
hermes/mobile/ios-app/Core/Networking/APIClient.swift
T

156 lines
5.3 KiB
Swift

import Foundation
struct APIEnvironment {
let baseURL: URL
init(baseURL: URL) {
self.baseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
}
}
enum HermesAPIError: Error {
case invalidURL(String)
case invalidResponse
case transport(Error)
case unexpectedStatus(Int, Data)
case decoding(Error)
}
struct HermesAPIClient {
let environment: APIEnvironment
let session: URLSession
private let encoder: JSONEncoder
private let decoder: JSONDecoder
init(environment: APIEnvironment, session: URLSession = .shared) {
self.environment = environment
self.session = session
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .iso8601
self.encoder = encoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
self.decoder = decoder
}
func startSession(_ payload: HermesSessionStartRequest) async throws -> HermesSessionResponse {
try await send(path: "api/v1/session/start", method: "POST", body: payload)
}
func health() async throws -> HermesHealthResponse {
try await send(path: "health")
}
func endSession() async throws -> HermesSessionResponse {
try await send(path: "api/v1/session/end", method: "POST")
}
func currentSession() async throws -> HermesSessionResponse {
try await send(path: "api/v1/session/me")
}
func nextEvent() async throws -> HermesEvent {
try await send(path: "api/v1/feed/next")
}
func eventManifest(eventID: UUID) async throws -> HermesEventManifest {
try await send(path: "api/v1/events/\(eventID.uuidString)/manifest")
}
func markets(eventID: UUID) async throws -> [HermesMarket] {
try await send(path: "api/v1/events/\(eventID.uuidString)/markets")
}
func currentOdds(marketID: UUID) async throws -> HermesOddsVersion {
try await send(path: "api/v1/markets/\(marketID.uuidString)/odds/current")
}
func submitBetIntent(_ payload: HermesBetIntentRequest) async throws -> HermesBetIntentResponse {
try await send(path: "api/v1/bets/intent", method: "POST", body: payload)
}
func betIntent(id: UUID) async throws -> HermesBetIntentResponse {
try await send(path: "api/v1/bets/\(id.uuidString)")
}
func settlement(eventID: UUID) async throws -> HermesSettlement {
try await send(path: "api/v1/events/\(eventID.uuidString)/result")
}
func experimentConfig() async throws -> HermesExperimentConfig {
try await send(path: "api/v1/experiments/config")
}
func localization(localeCode: String) async throws -> HermesLocalizationBundle {
try await send(path: "api/v1/localization/\(localeCode)")
}
func submitAnalyticsBatch(_ payload: HermesAnalyticsBatchRequest) async throws {
let encodedBody = try encoder.encode(payload)
_ = try await perform(path: "api/v1/analytics/batch", method: "POST", body: encodedBody)
}
private func send<Response: Decodable>(path: String, method: String = "GET") async throws -> Response {
let (data, _) = try await perform(path: path, method: method)
do {
return try decoder.decode(Response.self, from: data)
} catch {
throw HermesAPIError.decoding(error)
}
}
private func send<Response: Decodable, Body: Encodable>(path: String, method: String, body: Body) async throws -> Response {
let encodedBody = try encoder.encode(body)
let (data, _) = try await perform(path: path, method: method, body: encodedBody)
do {
return try decoder.decode(Response.self, from: data)
} catch {
throw HermesAPIError.decoding(error)
}
}
private func perform(path: String, method: String, body: Data? = nil) async throws -> (Data, HTTPURLResponse) {
let request = try makeRequest(path: path, method: method, body: body)
do {
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw HermesAPIError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
throw HermesAPIError.unexpectedStatus(httpResponse.statusCode, data)
}
return (data, httpResponse)
} catch let error as HermesAPIError {
throw error
} catch {
throw HermesAPIError.transport(error)
}
}
private func makeRequest(path: String, method: String, body: Data? = nil) throws -> URLRequest {
let normalizedPath = path.hasPrefix("/") ? String(path.dropFirst()) : path
guard let url = URL(string: normalizedPath, relativeTo: environment.baseURL)?.absoluteURL else {
throw HermesAPIError.invalidURL(path)
}
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("application/json", forHTTPHeaderField: "Accept")
if let body {
request.httpBody = body
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
return request
}
}