mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 01:39:46 +00:00
Yattee v2 rewrite
This commit is contained in:
163
Yattee/Services/Networking/APIError.swift
Normal file
163
Yattee/Services/Networking/APIError.swift
Normal file
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// APIError.swift
|
||||
// Yattee
|
||||
//
|
||||
// Typed errors for network operations.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Errors that can occur during API operations.
|
||||
enum APIError: Error, LocalizedError, Equatable, Sendable {
|
||||
/// The URL could not be constructed.
|
||||
case invalidURL
|
||||
|
||||
/// The request failed with an HTTP status code.
|
||||
case httpError(statusCode: Int, message: String?)
|
||||
|
||||
/// The response could not be decoded.
|
||||
case decodingError(String)
|
||||
|
||||
/// The request timed out.
|
||||
case timeout
|
||||
|
||||
/// No network connection available.
|
||||
case noConnection
|
||||
|
||||
/// The request was cancelled.
|
||||
case cancelled
|
||||
|
||||
/// Server returned an error message.
|
||||
case serverError(String)
|
||||
|
||||
/// Rate limited by the server.
|
||||
case rateLimited(retryAfter: TimeInterval?)
|
||||
|
||||
/// Authentication required or failed.
|
||||
case unauthorized
|
||||
|
||||
/// Resource not found, with optional server-provided detail message.
|
||||
case notFound(String?)
|
||||
|
||||
/// Comments are disabled for this video.
|
||||
case commentsDisabled
|
||||
|
||||
/// No suitable instance available.
|
||||
case noInstance
|
||||
|
||||
/// No playable streams available.
|
||||
case noStreams
|
||||
|
||||
/// The request parameters were invalid.
|
||||
case invalidRequest
|
||||
|
||||
/// Operation not supported by this instance type.
|
||||
case notSupported
|
||||
|
||||
/// An unknown error occurred.
|
||||
case unknown(String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL:
|
||||
return "Invalid URL"
|
||||
case .httpError(let statusCode, let message):
|
||||
if let message {
|
||||
return message
|
||||
}
|
||||
return "HTTP error: \(statusCode)"
|
||||
case .decodingError(let message):
|
||||
return "Failed to decode response: \(message)"
|
||||
case .timeout:
|
||||
return "Request timed out"
|
||||
case .noConnection:
|
||||
return "No network connection"
|
||||
case .cancelled:
|
||||
return "Request was cancelled"
|
||||
case .serverError(let message):
|
||||
return "Server error: \(message)"
|
||||
case .rateLimited(let retryAfter):
|
||||
if let retryAfter {
|
||||
return "Rate limited. Retry after \(Int(retryAfter)) seconds"
|
||||
}
|
||||
return "Rate limited"
|
||||
case .unauthorized:
|
||||
return "Authentication required"
|
||||
case .notFound(let detail):
|
||||
if let detail {
|
||||
return detail
|
||||
}
|
||||
return "Resource not found"
|
||||
case .commentsDisabled:
|
||||
return "Comments are disabled"
|
||||
case .noInstance:
|
||||
return "No suitable instance available"
|
||||
case .noStreams:
|
||||
return "No playable streams available"
|
||||
case .invalidRequest:
|
||||
return "Invalid request"
|
||||
case .notSupported:
|
||||
return "Operation not supported"
|
||||
case .unknown(let message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this error is likely recoverable by retrying.
|
||||
var isRetryable: Bool {
|
||||
switch self {
|
||||
case .timeout, .noConnection, .rateLimited:
|
||||
return true
|
||||
case .httpError(let statusCode, _):
|
||||
return statusCode >= 500 || statusCode == 429
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: APIError, rhs: APIError) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.invalidURL, .invalidURL),
|
||||
(.timeout, .timeout),
|
||||
(.noConnection, .noConnection),
|
||||
(.cancelled, .cancelled),
|
||||
(.unauthorized, .unauthorized),
|
||||
(.commentsDisabled, .commentsDisabled),
|
||||
(.noInstance, .noInstance),
|
||||
(.noStreams, .noStreams),
|
||||
(.invalidRequest, .invalidRequest),
|
||||
(.notSupported, .notSupported):
|
||||
return true
|
||||
case (.notFound(let lDetail), .notFound(let rDetail)):
|
||||
return lDetail == rDetail
|
||||
case (.httpError(let lCode, let lMsg), .httpError(let rCode, let rMsg)):
|
||||
return lCode == rCode && lMsg == rMsg
|
||||
case (.decodingError(let lMsg), .decodingError(let rMsg)):
|
||||
return lMsg == rMsg
|
||||
case (.serverError(let lMsg), .serverError(let rMsg)):
|
||||
return lMsg == rMsg
|
||||
case (.rateLimited(let lRetry), .rateLimited(let rRetry)):
|
||||
return lRetry == rRetry
|
||||
case (.unknown(let lMsg), .unknown(let rMsg)):
|
||||
return lMsg == rMsg
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a decoding error from a Swift DecodingError.
|
||||
static func decodingError(_ error: DecodingError) -> APIError {
|
||||
switch error {
|
||||
case .typeMismatch(let type, let context):
|
||||
return .decodingError("Type mismatch for \(type): \(context.debugDescription)")
|
||||
case .valueNotFound(let type, let context):
|
||||
return .decodingError("Value not found for \(type): \(context.debugDescription)")
|
||||
case .keyNotFound(let key, let context):
|
||||
return .decodingError("Key '\(key.stringValue)' not found: \(context.debugDescription)")
|
||||
case .dataCorrupted(let context):
|
||||
return .decodingError("Data corrupted: \(context.debugDescription)")
|
||||
@unknown default:
|
||||
return .decodingError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user