Include rate limit reset date in error message

Closes #548
This commit is contained in:
Shadowfacts 2024-12-15 13:27:09 -05:00
parent 242c60d74d
commit 82ec120871
1 changed files with 24 additions and 9 deletions

View File

@ -25,27 +25,30 @@ public struct Client: Sendable {
public var timeoutInterval: TimeInterval = 60 public var timeoutInterval: TimeInterval = 60
static let decoder: JSONDecoder = { private static let dateFormatter: DateFormatter = {
let decoder = JSONDecoder()
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ" formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
formatter.timeZone = TimeZone(abbreviation: "UTC") formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.locale = Locale(identifier: "en_US_POSIX") 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 decoder.dateDecodingStrategy = .custom({ (decoder) in
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
let str = try container.decode(String.self) let str = try container.decode(String.self)
// for the next time mastodon accidentally changes date formats >.> if let date = Self.decodeDate(string: str) {
if let date = formatter.date(from: str) {
return date
} else if let date = iso8601.date(from: str) {
return date return date
} else { } else {
throw DecodingError.typeMismatch(Date.self, .init(codingPath: container.codingPath, debugDescription: "unexpected date format: \(str)")) throw DecodingError.typeMismatch(Date.self, .init(codingPath: container.codingPath, debugDescription: "unexpected date format: \(str)"))
} }
}) })
return decoder return decoder
}() }()
@ -105,6 +108,15 @@ public struct Client: Sendable {
return task 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 @discardableResult
public func run<Result: Sendable>(_ request: Request<Result>) async throws -> (Result, Pagination?) { public func run<Result: Sendable>(_ request: Request<Result>) async throws -> (Result, Pagination?) {
return try await withCheckedThrowingContinuation { continuation in return try await withCheckedThrowingContinuation { continuation in
@ -575,6 +587,8 @@ extension Client {
return "Invalid Model" return "Invalid Model"
case .mastodonError(let code, let error): case .mastodonError(let code, let error):
return "Server Error (\(code)): \(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 invalidResponse
case invalidModel(Swift.Error) case invalidModel(Swift.Error)
case mastodonError(Int, String) case mastodonError(Int, String)
case rateLimited(Date)
} }
enum NodeInfoError: LocalizedError { enum NodeInfoError: LocalizedError {