Skip to content

Instantly share code, notes, and snippets.

@unboxme
Last active February 21, 2023 13:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save unboxme/8188dbfe1c631a054d3a16b6204cab80 to your computer and use it in GitHub Desktop.
Save unboxme/8188dbfe1c631a054d3a16b6204cab80 to your computer and use it in GitHub Desktop.
Allows to map enum to another using raw "case name"
import Foundation
/// Allows to map enum to another using raw "case name".
///
/// Let's say there are 2 enums such as `GenericEnum` (each case of that may have arguments)
/// and `PlainEnum` (each case of that are representable by a string):
///
/// enum GenericEnum<T>: CaseNameable {
/// case generic(value: T) // caseName == "generic"
/// case exact(Int) // caseName == "exact"
/// case empty // caseName == "empty"
/// }
///
/// enum PlainEnum: String, CaseNameMappable {
/// case generic // caseName == "generic"
/// case exact // caseName == "exact"
/// case empty // caseName == "empty" (used as default case)
/// }
///
/// So `PlainEnum` may easily mapped from `GenericEnum` using `CaseNameMappable.map(_:)`:
///
/// let genericCase = GenericEnum<String>.exact(3)
/// let plainCase = PlainEnum.map(genericCase)
/// assert(genericCase.caseName == plainCase.caseName) // "exact" == "exact"
///
public protocol CaseNameable {
/// Raw case name for all kind of enums.
var caseName: String { get }
}
extension CaseNameable {
public var caseName: String {
Mirror(reflecting: self).children.first?.label ?? String(describing: self)
}
}
public protocol CaseNameMappable: CaseNameable, CaseIterable {
associatedtype MappableSource
/// Creates instance of `Self` from `T`.
/// - Note: When the matches between `caseName` not found — the last case will be used.
static func map<MappableSource: CaseNameable>(_ input: MappableSource) -> Self
}
extension CaseNameMappable {
public static func map<T: CaseNameable>(_ input: T) -> Self {
let outputAllCases = Self.allCases as? [Self] ?? []
// It is safe to use subscript here because you can not create a enum without cases
let defaultValue = outputAllCases[outputAllCases.count - 1]
var value: Self?
outputAllCases.forEach { output in
if output.caseName == input.caseName {
value = output
}
}
if !isRunningTests {
let message = """
Enum cases of \(Self.self) or \(T.self) have been changed. Falled back to '\(defaultValue).'
"""
assert(value != nil, message)
}
return value ?? defaultValue
}
}
private var isRunningTests: Bool {
return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment