Files
2026-07-13 12:22:33 +08:00

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())
}
}