? Enum key paths

With the package you can use key paths with enums

import EnumKeyPaths

// MARK: - Authentication

enum Authentication {
    case authenticated(accessToken: String)
    case unauthenticated
}

/Authentication.authenticated // EnumKeyPath<Authentication, String>

user[keyPath: \User.id] = 113
user[keyPath: \User.id] // 113

let authentication = (/Authentication.authenticated).embed("access")
(/Authentication.authenticated).extract(from: authentication) // Optional("access")

Extraction can fail and return nil because the cases may not match up.

(/Authentication.authenticated).extract(from: .unauthenticated) // nil

Swift key paths use dot-syntax to dive deeper into a structure, enum key paths use a double-dot syntax:

\Table.user.name
// WritableKeyPath<Table, String>

/Result<Authentication, Error>..Authentication.authenticated
// EnumKeyPath<Result<Authentication, Error>, String>

Enum key paths provide an “identity” path, which is useful for interacting with APIs that use key paths but you want to work with entire structure.

\User.self           // WritableKeyPath<User, User>
/Authentication.self // EnumKeyPath<Authentication, Authentication>

Key paths are created for every property, even computed ones, so what is the equivalent for enum key paths? “computed” enum key paths can be created by providing custom embed and extract functions:

EnumKeyPath<Authentication, String>(
    embed: { token in
        Authentication.authenticated(token: encrypt(token))
    },
    extract: { authentication in
        guard
            case let .authenticated(encryptedToken) = authentication,
            let decryptedToken = decrypt(token)
            else { return nil }
        return decryptedToken
    }
)

Since Swift 5.2, key path expressions can be passed directly to methods like map. The same is true of enum key path expressions, which can be passed to methods like compactMap:

users.map(\User.name)
authentications.compactMap(/Authentication.authenticated)

Ergonomic associated value access

EnumKeyPaths uses Swift reflection to automatically and extract associated values from any enum in a single, short expression. This helpful utility is made available as a public module function that can be used in your own libraries and apps:

extract(case: Authentication.authenticated, from: .authenticated("token"))
// Optional("token")

Enum key paths operators

// With operators:
/Authentication.authenticated
// Without:
EnumKeyPath.case(Authentication.authenticated)

// With operators:
authentications.compactMap(/Authentication.authenticated)
// Without:
authentications.compactMap(extract(Authentication.authenticated))

// With operators:
/Result<Authentication, Error>.success..Authentication.authenticated
// Without:
EnumKeyPath.case(Result<Authentication, Error>.success)
  .appending(path: .case(Authentication.authenticated))

// With operators:
/Authentication.self
// Without operators:
EnumKeyPath<Authentication, Authentication>.self

Installation

You can add EnumKeyPaths to an Xcode project by adding it as a package dependency:
https://github.com/Incetro/enum-key-paths

If you want to use EnumKeyPaths in a SwiftPM project, it’s as simple as adding a dependencies clause to your Package.swift:

dependencies: [
    .package(url: "https://github.com/Incetro/enum-key-paths.git", from: "0.0.1")
]

License

All modules are released under the MIT license. See LICENSE for details.

GitHub

https://github.com/Incetro/enum-key-paths