diff --git a/Packages/Pachyderm/Sources/Pachyderm/Client.swift b/Packages/Pachyderm/Sources/Pachyderm/Client.swift index 906cf2ee..6065e841 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Client.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Client.swift @@ -25,27 +25,30 @@ public struct Client: Sendable { public var timeoutInterval: TimeInterval = 60 - static let decoder: JSONDecoder = { - let decoder = JSONDecoder() - + private static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ" formatter.timeZone = TimeZone(abbreviation: "UTC") formatter.locale = Locale(identifier: "en_US_POSIX") - let iso8601 = ISO8601DateFormatter() + return formatter + }() + private static let iso8601Formatter = ISO8601DateFormatter() + private static func decodeDate(string: String) -> Date? { + // for the next time mastodon accidentally changes date formats >.> + return dateFormatter.date(from: string) ?? iso8601Formatter.date(from: string) + } + + static let decoder: JSONDecoder = { + let decoder = JSONDecoder() decoder.dateDecodingStrategy = .custom({ (decoder) in let container = try decoder.singleValueContainer() let str = try container.decode(String.self) - // for the next time mastodon accidentally changes date formats >.> - if let date = formatter.date(from: str) { - return date - } else if let date = iso8601.date(from: str) { + if let date = Self.decodeDate(string: str) { return date } else { throw DecodingError.typeMismatch(Date.self, .init(codingPath: container.codingPath, debugDescription: "unexpected date format: \(str)")) } }) - return decoder }() @@ -105,6 +108,15 @@ public struct Client: Sendable { return task } + private func error(from response: HTTPURLResponse) -> ErrorType { + if response.statusCode == 429, + let date = response.value(forHTTPHeaderField: "X-RateLimit-Reset").flatMap(Self.decodeDate) { + return .rateLimited(date) + } else { + return .unexpectedStatus(response.statusCode) + } + } + @discardableResult public func run(_ request: Request) async throws -> (Result, Pagination?) { return try await withCheckedThrowingContinuation { continuation in @@ -575,6 +587,8 @@ extension Client { return "Invalid Model" case .mastodonError(let code, let error): return "Server Error (\(code)): \(error)" + case .rateLimited(let reset): + return "Rate Limited Until \(reset.formatted(date: .omitted, time: .standard))" } } } @@ -585,6 +599,7 @@ extension Client { case invalidResponse case invalidModel(Swift.Error) case mastodonError(Int, String) + case rateLimited(Date) } enum NodeInfoError: LocalizedError {