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
errorCodeFromStringnull45 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