90 lines
2.8 KiB
Swift
90 lines
2.8 KiB
Swift
import Foundation
|
|
#if canImport(FoundationNetworking)
|
|
import FoundationNetworking
|
|
#endif
|
|
|
|
public struct CrofUsageResponse: Decodable, Sendable {
|
|
public let credits: Double
|
|
public let requestsPlan: Double
|
|
public let usableRequests: Double
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case credits
|
|
case requestsPlan = "requests_plan"
|
|
case usableRequests = "usable_requests"
|
|
}
|
|
}
|
|
|
|
public enum CrofUsageError: LocalizedError, Sendable, Equatable {
|
|
case missingCredentials
|
|
case networkError(String)
|
|
case apiError(Int)
|
|
case parseFailed(String)
|
|
|
|
public var errorDescription: String? {
|
|
switch self {
|
|
case .missingCredentials:
|
|
"Missing Crof API key."
|
|
case let .networkError(message):
|
|
"Crof network error: \(message)"
|
|
case let .apiError(statusCode):
|
|
"Crof API error: HTTP \(statusCode)"
|
|
case let .parseFailed(message):
|
|
"Failed to parse Crof response: \(message)"
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum CrofUsageFetcher {
|
|
public static let usageURL = URL(string: "https://crof.ai/usage_api/")!
|
|
private static let timeoutSeconds: TimeInterval = 15
|
|
|
|
public static func fetchUsage(
|
|
apiKey: String,
|
|
session transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> CrofUsageSnapshot
|
|
{
|
|
let trimmed = apiKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else {
|
|
throw CrofUsageError.missingCredentials
|
|
}
|
|
|
|
var request = URLRequest(url: self.usageURL)
|
|
request.httpMethod = "GET"
|
|
request.timeoutInterval = Self.timeoutSeconds
|
|
request.setValue("Bearer \(trimmed)", forHTTPHeaderField: "Authorization")
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
|
|
let response: ProviderHTTPResponse
|
|
do {
|
|
response = try await transport.response(for: request)
|
|
} catch {
|
|
throw CrofUsageError.networkError(error.localizedDescription)
|
|
}
|
|
|
|
guard response.statusCode == 200 else {
|
|
throw CrofUsageError.apiError(response.statusCode)
|
|
}
|
|
|
|
return try self.parseSnapshot(response.data)
|
|
}
|
|
|
|
static func _parseSnapshotForTesting(_ data: Data) throws -> CrofUsageSnapshot {
|
|
try self.parseSnapshot(data)
|
|
}
|
|
|
|
private static func parseSnapshot(_ data: Data) throws -> CrofUsageSnapshot {
|
|
let decoded: CrofUsageResponse
|
|
do {
|
|
decoded = try JSONDecoder().decode(CrofUsageResponse.self, from: data)
|
|
} catch {
|
|
throw CrofUsageError.parseFailed(error.localizedDescription)
|
|
}
|
|
|
|
return CrofUsageSnapshot(
|
|
credits: decoded.credits,
|
|
requestsPlan: decoded.requestsPlan,
|
|
usableRequests: decoded.usableRequests,
|
|
updatedAt: Date())
|
|
}
|
|
}
|