Skip to content

Instantly share code, notes, and snippets.

@dehrom
Last active April 29, 2019 07:43
Show Gist options
  • Save dehrom/c8dc80bba531889ecc33e3071e79f0dd to your computer and use it in GitHub Desktop.
Save dehrom/c8dc80bba531889ecc33e3071e79f0dd to your computer and use it in GitHub Desktop.
import Foundation
private let errorDebugDescriptionCreateBlock: (String, ContentRepresentable & IntCodeRepresentable & LocalizedErrorRepresentable) -> String = { name, error in
return """
=== Self is \(name) ===
Code: \(error.code) \r
errorDescription: \(String(describing: error.asLocalizedError?.errorDescription)) \r
failureReason: \(String(describing: error.asLocalizedError?.failureReason)) \r
recoverySuggestion: \(String(describing: error.asLocalizedError?.recoverySuggestion)) \r
helpAnchor: \(String(describing: error.asLocalizedError?.helpAnchor)) \r
content: \(error.content.debugDescription) \r\n
"""
}
public protocol ContentRepresentable {
var content: [String: Any] { get }
}
public extension ContentRepresentable {
var content: [String: Any] {
return [:]
}
}
public protocol IntCodeRepresentable {
var code: Int { get }
}
public extension IntCodeRepresentable where Self: RawRepresentable, Self.RawValue == Int {
var code: Int {
return rawValue
}
}
public protocol LocalizedErrorRepresentable {
var asLocalizedError: LocalizedError? { get }
}
public extension LocalizedErrorRepresentable where Self: LocalizedError {
var asLocalizedError: LocalizedError? {
return self
}
}
public protocol ChainableErrorRepresentable: IntCodeRepresentable, LocalizedErrorRepresentable, ContentRepresentable, CustomDebugStringConvertible {}
public extension ChainableErrorRepresentable where Self: LocalizedError {
var debugDescription: String {
return errorDebugDescriptionCreateBlock("\(type(of: self))", self)
}
}
public protocol ChainableError: RawRepresentable, LocalizedError, ChainableErrorRepresentable where Self.RawValue == Int {}
extension ChainableError {
public func chained(_ error: Error, content: [String: Any] = [:]) -> Error {
switch error {
case let (chain as Chainable):
return chained(withChainable: chain, content: content)
case let (baseError as ChainableErrorRepresentable):
return chained(baseError, content: content)
default:
preconditionFailure("Unchainable error: \(error) of type \(type(of: error)). Please handle error previously!")
}
}
}
private extension ChainableError {
private func chained(withChainable error: Chainable, content: [String: Any]) -> Error {
var head = ChainErrorBox(from: self, content: content)
head.chain(with: error)
return head
}
private func chained(_ error: ChainableErrorRepresentable, content: [String: Any]) -> Error {
let box = ChainErrorBox(from: error, content: error.content)
return chained(withChainable: box, content: content)
}
}
internal protocol Chainable {
var previous: Chainable? { get }
mutating func chain(with previous: Chainable)
}
internal protocol ChainIntCodesCollectable: Chainable, ChainableErrorRepresentable {}
extension ChainIntCodesCollectable {
var chainedCodes: [Int] {
func collectFrom(_ node: Self?) -> [Int] {
guard let node = node else { return [] }
return [node.code] + collectFrom(node.previous as? Self)
}
return collectFrom(self)
}
}
private struct ChainErrorBox: ChainIntCodesCollectable, Error {
init(from error: ChainableErrorRepresentable, content: [String : Any]) {
boxed = error
self.content = content
}
var code: Int {
return boxed.code
}
var asLocalizedError: LocalizedError? {
return boxed.asLocalizedError
}
var debugDescription: String {
return errorDebugDescriptionCreateBlock("\(type(of: boxed))", self)
}
mutating func chain(with previous: Chainable) {
self.previous = previous
}
internal let content: [String : Any]
private let boxed: ChainableErrorRepresentable
private(set) var previous: Chainable?
}
extension ChainErrorBox: LocalizedError {
var errorDescription: String? {
return boxed.asLocalizedError?.errorDescription
}
var failureReason: String? {
return boxed.asLocalizedError?.failureReason
}
var recoverySuggestion: String? {
return boxed.asLocalizedError?.recoverySuggestion
}
var helpAnchor: String? {
return boxed.asLocalizedError?.helpAnchor
}
}
public struct ChainableErrorFormatter {
public let identificator: String
public init(identificator: String) {
self.identificator = identificator
}
public func string(for error: Error, defaultValue: String? = nil) -> String {
switch (error as? ChainErrorBox, defaultValue) {
case let (.some(error), _):
return "\(identificator)::\(error.chainedCodes.map { "\($0)" }.joined(separator: "-"))"
case (.none, .none):
assertionFailure("This error \(error) with type \(type(of: error.self)) could not be presented as chain!")
return "ILLEGAL ERROR TYPE"
case let (.none, .some(message)):
return message
}
}
}
enum ApiError: Int, Error {
case error0 = 4
case error1 = 6
}
extension ApiError: ChainableErrorRepresentable, LocalizedError {
static let atemptsCount = "atemptsCount"
var content: [String : Any] {
switch self {
case .error0:
return [ApiError.atemptsCount: code]
case .error1:
return [ApiError.atemptsCount: code]
}
}
}
enum ServiceError: Int, ChainableError {
case serverIsBusy = 7
case apiInternalError = 11
case `internal`
}
enum InteractorError: Int, ChainableError {
case `internal`
case logic = 4
}
final class TestErrors {
func test() {
// service
let apiResponse = Result<Data, ApiError>.failure(.error0)
let serviceError = { () -> Error in
switch (apiResponse) {
case let .failure(error) where error == ApiError.error0:
return ServiceError.apiInternalError.chained(error, content: error.content)
case let .failure(error) where error == ApiError.error1:
return ServiceError.serverIsBusy.chained(error, content: error.content)
default:
return ServiceError.internal
}
}()
/*
=== Self is ServiceError ===
Code: 11
errorDescription: nil
failureReason: nil
recoverySuggestion: nil
helpAnchor: nil
content: ["atemptsCount": 4]
*/
// interactor
let interactorError = { () -> Error in
switch (serviceError as? ChainableErrorRepresentable) {
case let (.some(error)) where error.code == ServiceError.apiInternalError.rawValue:
return InteractorError.logic.chained(serviceError, content: error.content)
default:
return InteractorError.internal.chained(serviceError)
}
}()
/*
=== Self is InteractorError ===
Code: 4
errorDescription: nil
failureReason: nil
recoverySuggestion: nil
helpAnchor: nil
content: ["atemptsCount": 4]
*/
let str = ChainableErrorFormatter(identificator: "TEST").string(for: interactorError)
print(str) // "TEST::4-11-4"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment