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     let className = String(describing: type(of: self))
21       .replacingOccurrences(of: #"(Error|Exception)?(<.*>)?$"#, with: "", options: .regularExpression)
22     let regex = try! NSRegularExpression(pattern: "(.)([A-Z])", options: [])
23     let range = NSRange(location: 0, length: className.count)
24 
25     return "ERR_" + regex
26       .stringByReplacingMatches(in: className, options: [], range: range, withTemplate: "$1_$2")
27       .uppercased()
28   }
29 
30   /**
31    The description falls back to object's localized description.
32    */
33   var description: String {
34     return localizedDescription
35   }
36 }
37 
38 /**
39  Basic implementation of `CodedError` protocol,
40  where the code and the description are provided in the initializer.
41  */
42 public struct SimpleCodedError: CodedError {
43   public var code: String
44   public var description: String
45 
46   init(_ code: String, _ description: String) {
47     self.code = code
48     self.description = description
49   }
50 }
51