1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 /**
4  A protocol that allows converting raw values to enum cases.
5  */
6 public protocol Enumerable: AnyArgument, CaseIterable {
7   /**
8    Tries to create an enum case using given raw value.
9    May throw errors, e.g. when the raw value doesn't match any case.
10    */
create<RawValueType>null11   static func create<RawValueType>(fromRawValue rawValue: RawValueType) throws -> Self
12 
13   /**
14    Returns an array of all raw values available in the enum.
15    */
16   static var allRawValues: [Any] { get }
17 
18   /**
19    Type-erased enum's raw value.
20    */
21   var anyRawValue: Any { get }
22 }
23 
24 @available(*, deprecated, renamed: "Enumerable")
25 public typealias EnumArgument = Enumerable
26 
27 /**
28  Extension for `Enumerable` that also conforms to `RawRepresentable`.
29  This constraint allows us to reference the associated `RawValue` type.
30  */
31 public extension Enumerable where Self: RawRepresentable, Self: Hashable {
create<ArgType>null32   static func create<ArgType>(fromRawValue rawValue: ArgType) throws -> Self {
33     guard let rawValue = rawValue as? RawValue else {
34       throw EnumCastingException((type: RawValue.self, value: rawValue))
35     }
36     guard let enumCase = Self.init(rawValue: rawValue) else {
37       throw EnumNoSuchValueException((type: Self.self, value: rawValue))
38     }
39     return enumCase
40   }
41 
42   var anyRawValue: Any {
43     rawValue
44   }
45 
46   static var allRawValues: [Any] {
47     return allCases.map { $0.rawValue }
48   }
49 }
50 
51 /**
52  An error that is thrown when the value cannot be cast to associated `RawValue`.
53  */
54 internal class EnumCastingException: GenericException<(type: Any.Type, value: Any)> {
55   override var reason: String {
56     "Unable to cast '\(param.value)' to expected type \(param.type)"
57   }
58 }
59 
60 /**
61  An error that is thrown when the value doesn't match any available case.
62  */
63 internal class EnumNoSuchValueException: GenericException<(type: Enumerable.Type, value: Any)> {
64   var allRawValuesFormatted: String {
65     return param.type.allRawValues
66       .map { "'\($0)'" }
67       .joined(separator: ", ")
68   }
69 
70   override var reason: String {
71     "'\(param.value)' is not present in \(param.type) enum, it must be one of: \(allRawValuesFormatted)"
72   }
73 }
74