1 import Foundation 2 3 /** 4 A protocol for errors specyfing its `code` and providing the `description`. 5 */ 6 public protocol CodedError: Error, CustomStringConvertible { 7 var code: String { get } 8 var description: String { get } 9 } 10 11 /** 12 Extends the `CodedError` to make a fallback for `code` and `description`. 13 */ 14 public extension CodedError { 15 /** 16 The code is inferred from the class name — e.g. the code of `ModuleNotFoundError` becomes `ERR_MODULE_NOT_FOUND`. 17 To obtain the code, the class name is cut off from generics and `Error` suffix, then it's converted to snake case and uppercased. 18 */ 19 var code: String { 20 return errorCodeFromString(String(describing: type(of: self))) 21 } 22 23 /** 24 The description falls back to object's localized description. 25 */ 26 var description: String { 27 return localizedDescription 28 } 29 } 30 31 /** 32 Basic implementation of `CodedError` protocol, 33 where the code and the description are provided in the initializer. 34 */ 35 public struct SimpleCodedError: CodedError { 36 public var code: String 37 public var description: String 38 39 init(_ code: String, _ description: String) { 40 self.code = code 41 self.description = description 42 } 43 } 44 45 func errorCodeFromString(_ str: String) -> String { 46 let name = str.replacingOccurrences(of: #"(Error|Exception)?(<.*>)?$"#, with: "", options: .regularExpression) 47 // The pattern is valid, so it'll never throw 48 // swiftlint:disable:next force_try 49 let regex = try! NSRegularExpression(pattern: "(.)([A-Z])", options: []) 50 let range = NSRange(location: 0, length: name.count) 51 52 return "ERR_" + regex 53 .stringByReplacingMatches(in: name, options: [], range: range, withTemplate: "$1_$2") 54 .uppercased() 55 } 56