Created
March 17, 2022 18:19
-
-
Save pwwilson/643fe222477f6a89c6fa5f6f89003c57 to your computer and use it in GitHub Desktop.
Backflip Error Messaging
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// APIError.swift | |
// Backflip | |
// | |
// Created by Patrick Wilson on 11/16/21. | |
// | |
import Foundation | |
public enum APIError { | |
case noInternetConnection | |
case requestEncodingFailed // Request parameters and/or body encoding failed | |
case responseDecodingFailed // Response object could not be decoded | |
case badUrl // Could not generate a valid url | |
case clientErrorStatusCode(statusCode: Int, errorResponse: APIErrorResponse?) | |
case serverError // Server returned an generic error | |
case serverReturnedErrorMessage(message: String) // server returned a specific error message | |
case serverOverload // 503 response | |
case serverBadGateway // 502 response | |
case notFound // 404 response | |
case timeout // 408 response | |
case unauthorized(requestURL: URL?) // 401 status code | |
case context // Error occurred w/in View's Context | |
} | |
extension APIError: AppError { | |
public var errorDescription: String? { | |
switch self { | |
case .noInternetConnection: | |
return "no internet connection" | |
case .requestEncodingFailed: | |
return "failed to encode request" | |
case .responseDecodingFailed: | |
return "failed to decode server response" | |
case .badUrl: | |
return "invalid url" | |
case .clientErrorStatusCode(let statusCode, let errorResponse): | |
if let response = errorResponse { | |
return response.error | |
} | |
return "client error status code \(statusCode)" | |
case .serverError: | |
return "server error" | |
case .serverReturnedErrorMessage(let message): | |
return "server generated error message: \(message)" | |
case .serverBadGateway: | |
return "bad gateway" | |
case .serverOverload: | |
return "server overload" | |
case .notFound: | |
return "resource not found" | |
case .timeout: | |
return "timeout" | |
case .unauthorized: | |
return "unauthorized" | |
case .context: | |
return "context error" | |
} | |
} | |
// swiftlint:disable cyclomatic_complexity | |
public func getUserMessage() -> String { | |
switch self { | |
case .noInternetConnection: | |
return "Sorry, no network connection" | |
case .requestEncodingFailed, .responseDecodingFailed, .badUrl, .serverError: | |
return "Sorry, service issues" | |
case .clientErrorStatusCode(let statusCode, let errorResponse) where statusCode == 500: | |
return "Sorry, server 500 error \(String(describing: errorResponse))" | |
case .clientErrorStatusCode(let statusCode, let errorResponse): | |
if let response = errorResponse { | |
return "\(response.displayError) (\(statusCode))" | |
} | |
let unexpectedMsg = "Sorry: error code" | |
return "\(unexpectedMsg) \(statusCode)" | |
case .serverReturnedErrorMessage(let message): | |
return message | |
case .serverBadGateway: | |
return "Sorry, bad gateway (502 error) received" | |
case .serverOverload: | |
return "Sorry, high volume/server overload" | |
case .notFound: | |
return "Sorry, we could not find the data that you seek" | |
case .timeout: | |
return "Sorry, request timed out" | |
case .unauthorized: | |
return "Status 401 Unauthorized" | |
case .context: | |
return "Sorry, an unknown error has occurred in this context" | |
} | |
} | |
public func getHttpStatusCode() -> Int { | |
switch self { | |
case .noInternetConnection: | |
return 0 | |
case .requestEncodingFailed, .responseDecodingFailed, .badUrl, .context: | |
return 0 | |
case .clientErrorStatusCode(let statusCode, _): | |
return statusCode | |
case .serverReturnedErrorMessage: | |
return 0 | |
case .serverBadGateway: | |
return 502 | |
case .serverOverload: | |
return 503 | |
case .notFound: | |
return 404 | |
case .timeout: | |
return 408 | |
case .serverError: | |
return 0 | |
case .unauthorized: | |
return 401 | |
} | |
} | |
} | |
// MARK: - Equatable | |
extension APIError: Equatable { | |
public static func == (lhs: APIError, rhs: APIError) -> Bool { | |
switch (lhs, rhs) { | |
case (let .serverReturnedErrorMessage(lhsMessage), let .serverReturnedErrorMessage(rhsMessage)): | |
return lhsMessage == rhsMessage | |
case (let .clientErrorStatusCode(lhsStatusCode, _), let .clientErrorStatusCode(rhsStatusCode, _)): | |
return lhsStatusCode == rhsStatusCode | |
case (.noInternetConnection, .noInternetConnection), | |
(.requestEncodingFailed, requestEncodingFailed), | |
(.responseDecodingFailed, .responseDecodingFailed), | |
(.badUrl, .badUrl), | |
(.serverError, .serverError), | |
(.serverOverload, .serverOverload), | |
(.timeout, .timeout): | |
return true | |
default: | |
return false | |
} | |
} | |
} | |
// MARK: - CustomNSError | |
extension APIError: CustomNSError { | |
public static var errorDomain: String { | |
return String(reflecting: self) | |
} | |
public var errorCode: Int { | |
return self.getHttpStatusCode() | |
} | |
public var errorUserInfo: [String: Any] { | |
var userInfo: [String: Any] = [ | |
NSLocalizedDescriptionKey: self.errorDescription ?? "Error Description Unavailable", | |
NSLocalizedFailureReasonErrorKey: self.getUserMessage() | |
] | |
// When needed, we can set other values in the `userInfo` dictionary | |
// for specific errors, using `NSError.UserInfoKey` constants as keys. | |
switch self { | |
case .unauthorized(let requestURL): | |
userInfo[NSURLErrorKey] = requestURL?.absoluteString ?? "Request URL unavailable" | |
default: | |
break | |
} | |
return userInfo | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment