{"id":13872003,"url":"https://github.com/pointfreeco/swift-case-paths","last_synced_at":"2026-04-02T21:40:33.729Z","repository":{"id":38077366,"uuid":"238219958","full_name":"pointfreeco/swift-case-paths","owner":"pointfreeco","description":"🧰 Case paths extends the key path hierarchy to enum cases.","archived":false,"fork":false,"pushed_at":"2025-04-15T16:25:07.000Z","size":421,"stargazers_count":980,"open_issues_count":1,"forks_count":119,"subscribers_count":19,"default_branch":"main","last_synced_at":"2025-05-08T01:44:49.505Z","etag":null,"topics":["keypaths","swift"],"latest_commit_sha":null,"homepage":"https://www.pointfree.co/collections/enums-and-structs/case-paths","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pointfreeco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-02-04T14:02:40.000Z","updated_at":"2025-04-29T09:09:10.000Z","dependencies_parsed_at":"2023-09-26T22:33:52.712Z","dependency_job_id":"37beb536-1b30-4c51-b258-af3e013d0d04","html_url":"https://github.com/pointfreeco/swift-case-paths","commit_stats":{"total_commits":177,"total_committers":27,"mean_commits":6.555555555555555,"dds":0.3220338983050848,"last_synced_commit":"dc49b7a0dae1030eb99075a5217f182bcccbd07b"},"previous_names":[],"tags_count":50,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-case-paths","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-case-paths/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-case-paths/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-case-paths/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pointfreeco","download_url":"https://codeload.github.com/pointfreeco/swift-case-paths/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254101558,"owners_count":22014908,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["keypaths","swift"],"created_at":"2024-08-05T23:00:32.136Z","updated_at":"2026-04-02T21:40:33.684Z","avatar_url":"https://github.com/pointfreeco.png","language":"Swift","funding_links":[],"categories":["Swift"],"sub_categories":[],"readme":"# 🧰 CasePaths\n\n[![CI](https://github.com/pointfreeco/swift-case-paths/workflows/CI/badge.svg)](https://actions-badge.atrox.dev/pointfreeco/swift-case-paths/goto)\n[![Slack](https://img.shields.io/badge/slack-chat-informational.svg?label=Slack\u0026logo=slack)](http://pointfree.co/slack-invite)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpointfreeco%2Fswift-case-paths%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pointfreeco/swift-case-paths)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpointfreeco%2Fswift-case-paths%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pointfreeco/swift-case-paths)\n\nCase paths extends the key path hierarchy to enum cases.\n\n## Motivation\n\nSwift endows every struct and class property with a [key path][key-path-docs].\n\n[key-path-docs]: https://developer.apple.com/documentation/swift/swift_standard_library/key-path_expressions\n\n``` swift\nstruct User {\n  let id: Int\n  var name: String\n}\n\n\\User.id    // KeyPath\u003cUser, Int\u003e\n\\User.name  // WritableKeyPath\u003cUser, String\u003e\n```\n\nThis is compiler-generated code that can be used to abstractly zoom in on part of a structure,\ninspect and even change it, all while propagating those changes to the structure's whole. They are\nthe silent partner of many modern Swift APIs powered by\n[dynamic member lookup][dynamic-member-lookup-proposal], like SwiftUI\n[bindings][binding-dynamic-member-lookup-docs], but also make more direct appearances, like in the\nSwiftUI [environment][environment-property-wrapper-docs] and [unsafe mutable pointers][pointee].\n\n[pointee]: https://developer.apple.com/documentation/swift/unsafemutablepointer/pointer(to:)-8veyb\n\nUnfortunately, no such structure exists for enum cases.\n\n``` swift\nenum UserAction {\n  case home(HomeAction)\n  case settings(SettingsAction)\n}\n\n\\UserAction.home  // 🛑\n```\n\n\u003e 🛑 key path cannot refer to static member 'home'\n\nAnd so it's not possible to write generic code that can zoom in and modify the data of a particular\ncase in the enum.\n\n[key-path-docs]: https://developer.apple.com/documentation/swift/swift_standard_library/key-path_expressions\n[dynamic-member-lookup-proposal]: https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md\n[binding-dynamic-member-lookup-docs]: https://developer.apple.com/documentation/swiftui/bindable/subscript(dynamicmember:)\n[environment-property-wrapper-docs]: https://developer.apple.com/documentation/swiftui/scene/environment(_:_:)\n[combine-publisher-assign-docs]: https://developer.apple.com/documentation/combine/publisher/assign(to:on:)\n\n## Using case paths in libraries\n\nBy far the most common use of case paths is as a tool inside a library that is distributed to other\ndevelopers. Case paths are used in the [Composable Architecture][tca-gh],\n[SwiftUI Navigation][sui-nav-gh], [Parsing][parsers-gh], and many other libraries.\n\n[tca-gh]: http://github.com/pointfreeco/swift-composable-architecture\n[sui-nav-gh]: http://github.com/pointfreeco/swiftui-navigation\n[parsers-gh]: http://github.com/pointfreeco/swift-parsing\n\nIf you maintain a library where you expect your users to model their domains with enums, then\nproviding case path tools to them can help them break their domains into smaller units. For\nexample, consider the `Binding` type provided by SwiftUI:\n\n```swift\nstruct Binding\u003cValue\u003e {\n  let get: () -\u003e Value\n  let set: (Value) -\u003e Void\n}\n```\n\nThrough the power of [dynamic member lookup][dynamic-member-lookup-proposal] we are able to support\ndot-chaining syntax for deriving new bindings to members of values:\n\n```swift\n@dynamicMemberLookup\nstruct Binding\u003cValue\u003e {\n  …\n  subscript\u003cMember\u003e(dynamicMember keyPath: WritableKeyPath\u003cValue, Member\u003e) -\u003e Binding\u003cMember\u003e {\n    Binding\u003cMember\u003e(\n      get: { self.get()[keyPath: keyPath] },\n      set: { \n        var value = self.get()\n        value[keyPath: keyPath] = $0\n        self.set(value)\n      }\n    )\n  }\n}\n```\n\nIf you had a binding of a user, you could simply append `.name` to that binding to immediately\nderive a binding to the user's name:\n\n```swift\nlet user: Binding\u003cUser\u003e = // ...\nlet name: Binding\u003cString\u003e = user.name\n```\n\nHowever, there are no such affordances for enums:\n\n```swift\nenum Destination {\n  case home(HomeState)\n  case settings(SettingsState)\n}\nlet destination: Binding\u003cDestination\u003e = // ...\ndestination.home      // 🛑\ndestination.settings  // 🛑\n```\n\nIt is not possible to derive a binding to just the `home` case of a destination binding by using\nsimple dot-chaining syntax.\n\nHowever, if SwiftUI used this CasePaths library, then they could provide this tool quite easily.\nThey could provide an additional `dynamicMember` subscript that uses a `CaseKeyPath`, which is a\nkey path that singles out a case of an enum, and use that to derive a binding to a particular\ncase of an enum:\n\n```swift\nimport CasePaths\n\nextension Binding {\n  public subscript\u003cCase\u003e(dynamicMember keyPath: CaseKeyPath\u003cValue, Case\u003e) -\u003e Binding\u003cCase\u003e?\n  where Value: CasePathable {\n    Binding\u003cCase\u003e(\n      unwrapping: Binding\u003cCase?\u003e(\n        get: { self.wrappedValue[case: keyPath] },\n        set: { newValue, transaction in\n          guard let newValue else { return }\n          self.transaction(transaction).wrappedValue[case: keyPath] = newValue\n        }\n      )\n    )\n  }\n}\n```\n\nWith that defined, one can annotate their enum with the `@CasePathable` macro and then immediately\nuse dot-chaining to derive a binding of a case from a binding of an enum:\n\n```swift\n@CasePathable\nenum Destination {\n  case home(HomeState)\n  case settings(SettingsState)\n}\nlet destination: Binding\u003cDestination\u003e = // ...\ndestination.home      // Binding\u003cHomeState\u003e?\ndestination.settings  // Binding\u003cSettingsState\u003e?\n```\n\nThis is an example of how libraries can provide tools for their users to embrace enums without\nlosing out on the ergonomics of structs. \n\n## Basics of case paths\n\nWhile library tooling is the biggest use case for using this library, there are some ways that you\ncan use case paths in first-party code too. The library bridges the gap between structs and enums by\nintroducing what we call \"case paths\": key paths for enum cases.\n\nCase paths can be enabled for an enum using the `@CasePathable` macro:\n\n```swift\n@CasePathable\nenum UserAction {\n  case home(HomeAction)\n  case settings(SettingsAction)\n}\n```\n\nAnd they can be produced from a \"case-pathable\" enum through its `Cases` namespace:\n\n```swift\n\\UserAction.Cases.home      // CaseKeyPath\u003cUserAction, HomeAction\u003e\n\\UserAction.Cases.settings  // CaseKeyPath\u003cUserAction, SettingsAction\u003e\n```\n\nAnd like any key path, they can be abbreviated when the enum type can be inferred:\n\n```swift\n\\.home as CaseKeyPath\u003cUserAction, HomeAction\u003e\n\\.settings as CaseKeyPath\u003cUserAction, SettingsAction\u003e\n```\n\n### Case paths vs. key paths\n\n#### Extracting, embedding, modifying, and testing values\n\nAs key paths package up the functionality of getting and setting a value on a root structure, case\npaths package up the functionality of optionally extracting and modifying an associated value of a\nroot enumeration.\n\n``` swift\nuser[keyPath: \\User.name] = \"Blob\"\nuser[keyPath: \\.name]  // \"Blob\"\n\nuserAction[case: \\UserAction.Cases.home] = .onAppear\nuserAction[case: \\.home]  // Optional(HomeAction.onAppear)\n```\n\nIf the case doesn't match, the extraction can fail and return `nil`:\n\n```swift\nuserAction[case: \\.settings]  // nil\n```\n\nCase paths have an additional ability, which is to embed an associated value into a brand new root:\n\n```swift\nlet userActionToHome = \\UserAction.Cases.home\nuserActionToHome(.onAppear)  // UserAction.home(.onAppear)\n```\n\nCases can be tested using the `is` method on case-pathable enums:\n\n```swift\nuserAction.is(\\.home)      // true\nuserAction.is(\\.settings)  // false\n\nlet actions: [UserAction] = […]\nlet homeActionsCount = actions.count(where: { $0.is(\\.home) })\n```\n\nAnd their associated values can be mutated in place using the `modify` method:\n\n```swift\nvar result = Result\u003cString, Error\u003e.success(\"Blob\")\nresult.modify(\\.success) {\n  $0 += \", Jr.\"\n}\nresult  // Result.success(\"Blob, Jr.\")\n```\n\n#### Composing paths\n\nCase paths, like key paths, compose. You can dive deeper into the enumeration of an enumeration's\ncase using familiar dot-chaining:\n\n``` swift\n\\HighScore.user.name\n// WritableKeyPath\u003cHighScore, String\u003e\n\n\\AppAction.Cases.user.home\n// CaseKeyPath\u003cAppAction, HomeAction\u003e\n```\n\nOr you can append them together:\n\n```swift\nlet highScoreToUser = \\HighScore.user\nlet userToName = \\User.name\nlet highScoreToUserName = highScoreToUser.append(path: userToName)\n// WritableKeyPath\u003cHighScore, String\u003e\n\nlet appActionToUser = \\AppAction.Cases.user\nlet userActionToHome = \\UserAction.Cases.home\nlet appActionToHome = appActionToUser.append(path: userActionToHome)\n// CaseKeyPath\u003cAppAction, HomeAction\u003e\n```\n\n#### Identity paths\n\nCase paths, also like key paths, provide an\n[identity](https://github.com/apple/swift-evolution/blob/master/proposals/0227-identity-keypath.md)\npath, which is useful for interacting with APIs that use key paths and case paths but you want to\nwork with entire structure.\n\n``` swift\n\\User.self              // WritableKeyPath\u003cUser, User\u003e\n\\UserAction.Cases.self  // CaseKeyPath\u003cUserAction, UserAction\u003e\n```\n\n#### Property access\n\nSince Swift 5.2, key path expressions can be passed directly to methods like `map`. Case-pathable\nenums that are annotated with dynamic member lookup enable property access and key path expressions\nfor each case.\n\n```swift\n@CasePathable\n@dynamicMemberLookup\nenum UserAction {\n  case home(HomeAction)\n  case settings(SettingsAction)\n}\n\nlet userAction: UserAction = .home(.onAppear)\nuserAction.home      // Optional(HomeAction.onAppear)\nuserAction.settings  // nil\n\nlet userActions: [UserAction] = [.home(.onAppear), .settings(.purchaseButtonTapped)]\nuserActions.compactMap(\\.home)  // [HomeAction.onAppear]\n```\n\n#### Dynamic case lookup\n\nBecause case key paths are bona fide key paths under the hood, they can be used in the same\napplications, like dynamic member lookup. For example, we can extend SwiftUI's binding type to enum\ncases by extending it with a subscript:\n\n```swift\nextension Binding {\n  subscript\u003cMember\u003e(\n    dynamicMember keyPath: CaseKeyPath\u003cValue, Member\u003e\n  ) -\u003e Binding\u003cMember\u003e? {\n    guard let member = self.wrappedValue[case: keyPath]\n    else { return nil }\n    return Binding\u003cMember\u003e(\n      get: { self.wrappedValue[case: keyPath] ?? member },\n      set: { self.wrappedValue[case: keyPath] = $0 }\n    )\n  }\n}\n\n@CasePathable enum ItemStatus {\n  case inStock(quantity: Int)\n  case outOfStock(isOnBackOrder: Bool)\n}\n\nstruct ItemStatusView: View {\n  @Binding var status: ItemStatus\n\n  var body: some View {\n    switch self.status {\n    case .inStock:\n      self.$status.inStock.map { $quantity in\n        Section {\n          Stepper(\"Quantity: \\(quantity)\", value: $quantity)\n          Button(\"Mark as sold out\") {\n            self.item.status = .outOfStock(isOnBackOrder: false)\n          }\n        } header: {\n          Text(\"In stock\")\n        }\n      }\n    case .outOfStock:\n      self.$status.outOfStock.map { $isOnBackOrder in\n        Section {\n          Toggle(\"Is on back order?\", isOn: $isOnBackOrder)\n          Button(\"Is back in stock!\") {\n            self.item.status = .inStock(quantity: 1)\n          }\n        } header: {\n          Text(\"Out of stock\")\n        }\n      }\n    }\n  }\n}\n```\n\n\u003e **Note**\n\u003e The above is a simplified version of the subscript that ships in our\n\u003e [SwiftUINavigation](https://github.com/pointfreeco/swiftui-navigation) library.\n\n#### Computed paths\n\nKey paths are created for every property, even computed ones, so what is the equivalent for case\npaths? Well, \"computed\" case paths can be created by extending the case-pathable enum's\n`AllCasePaths` type with properties that implement the `embed` and `extract` functionality of a\ncustom case:\n\n```swift\n@CasePathable\nenum Authentication {\n  case authenticated(accessToken: String)\n  case unauthenticated\n}\n\nextension Authentication.AllCasePaths {\n  var encrypted: AnyCasePath\u003cAuthentication, String\u003e {\n    AnyCasePath(\n      embed: { decryptedToken in\n        .authenticated(token: encrypt(decryptedToken))\n      },\n      extract: { authentication in\n        guard\n          case let .authenticated(encryptedToken) = authentication,\n          let decryptedToken = decrypt(token)\n        else { return nil }\n        return decryptedToken\n      }\n    )\n  }\n}\n\n\\Authentication.Cases.encrypted\n// CaseKeyPath\u003cAuthentication, String\u003e\n```\n\n## Case studies\n\n  * [**SwiftUINavigation**](https://github.com/pointfreeco/swiftui-navigation) uses case paths to\n    power SwiftUI bindings, including navigation, with enums.\n\n  * [**The Composable Architecture**](https://github.com/pointfreeco/swift-composable-architecture)\n    allows you to break large features down into smaller ones that can be glued together user key\n    paths and case paths.\n\n  * [**Parsing**](https://github.com/pointfreeco/swift-parsing) uses case paths to turn unstructured\n    data into enums and back again.\n\nDo you have a project that uses case paths that you'd like to share? Please\n[open a PR](https://github.com/pointfreeco/swift-case-paths/edit/main/README.md) with a link to it!\n\n## Community\n\nIf you want to discuss this library or have a question about how to use it to solve a particular\nproblem, there are a number of places you can discuss with fellow\n[Point-Free](http://www.pointfree.co) enthusiasts:\n\n  * For long-form discussions, we recommend the\n    [discussions](http://github.com/pointfreeco/swift-case-paths/discussions) tab of this repo.\n  * For casual chat, we recommend the\n    [Point-Free Community Slack](http://pointfree.co/slack-invite).\n\n## Documentation\n\nThe latest documentation for CasePaths' APIs is available\n[here](https://swiftpackageindex.com/pointfreeco/swift-case-paths/main/documentation/casepaths).\n\n## Credit and thanks\n\nSpecial thanks to [Giuseppe Lanza](https://github.com/gringoireDM), whose\n[EnumKit](https://github.com/gringoireDM/EnumKit) inspired the original, reflection-based solution\nthis library used to power case paths.\n\n## Interested in learning more?\n\nThese concepts (and more) are explored thoroughly in [Point-Free](https://www.pointfree.co), a video\nseries exploring functional programming and Swift hosted by\n[Brandon Williams](https://github.com/mbrandonw) and\n[Stephen Celis](https://github.com/stephencelis).\n\nThe design of this library was explored in the following [Point-Free](https://www.pointfree.co)\nepisodes:\n\n  * [Episode 87](https://www.pointfree.co/episodes/ep87-the-case-for-case-paths-introduction): The\n    Case for Case Paths: Introduction\n  * [Episode 88](https://www.pointfree.co/episodes/ep88-the-case-for-case-paths-properties): The\n    Case for Case Paths: Properties\n  * [Episode 89](https://www.pointfree.co/episodes/ep89-case-paths-for-free): Case Paths for Free\n\n\u003ca href=\"https://www.pointfree.co/episodes/ep87-the-case-for-case-paths-introduction\"\u003e\n  \u003cimg alt=\"video poster image\" src=\"https://d3rccdn33rt8ze.cloudfront.net/episodes/0087.jpeg\" width=\"480\"\u003e\n\u003c/a\u003e\n\n## License\n\nAll modules are released under the MIT license. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointfreeco%2Fswift-case-paths","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpointfreeco%2Fswift-case-paths","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointfreeco%2Fswift-case-paths/lists"}