{"id":16431423,"url":"https://github.com/stephencelis/scptest","last_synced_at":"2025-08-17T13:19:10.171Z","repository":{"id":66258196,"uuid":"283509897","full_name":"stephencelis/scptest","owner":"stephencelis","description":null,"archived":false,"fork":false,"pushed_at":"2020-07-29T14:48:04.000Z","size":39,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-25T09:47:31.008Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/stephencelis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"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}},"created_at":"2020-07-29T13:42:38.000Z","updated_at":"2020-08-03T02:45:00.000Z","dependencies_parsed_at":"2023-02-21T09:00:17.897Z","dependency_job_id":null,"html_url":"https://github.com/stephencelis/scptest","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/stephencelis/scptest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stephencelis%2Fscptest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stephencelis%2Fscptest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stephencelis%2Fscptest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stephencelis%2Fscptest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stephencelis","download_url":"https://codeload.github.com/stephencelis/scptest/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stephencelis%2Fscptest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270852857,"owners_count":24656973,"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","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-10-11T08:30:06.307Z","updated_at":"2025-08-17T13:19:10.148Z","avatar_url":"https://github.com/stephencelis.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🧰 CasePaths\n\n[![Swift 5.1](https://img.shields.io/badge/swift-5.1-ED523F.svg?style=flat)](https://swift.org/download/)\n[![CI](https://github.com/pointfreeco/swift-case-paths/workflows/CI/badge.svg)](https://actions-badge.atrox.dev/pointfreeco/swift-case-paths/goto)\n[![@pointfreeco](https://img.shields.io/badge/contact-@pointfreeco-5AA9E7.svg?style=flat)](https://twitter.com/pointfreeco)\n\nCase paths bring the power and ergonomics of key paths to enums!\n\n## Motivation\n\nSwift endows every struct and class property with a [key path](https://developer.apple.com/documentation/swift/swift_standard_library/key-path_expressions).\n\n``` swift\nstruct User {\n  var id: Int\n  var name: String\n}\n\n\\User.id   // WritableKeyPath\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, inspect and even change it, while propagating these changes to the structure's whole. They unlock the ability to do many things, like [key-value observing](https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift) and [reactive bindings](https://developer.apple.com/documentation/combine/receiving_and_handling_events_with_combine), [dynamic member lookup](https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md), and scoping changes to the SwiftUI [environment](https://developer.apple.com/documentation/swiftui/environment).\n\nUnfortunately, no such structure exists for enum cases!\n\n``` swift\nenum Authentication {\n  case authenticated(accessToken: String)\n  case unauthenticated\n}\n\n\\Authentication.authenticated // 🛑\n```\n\nAnd so it's impossible to write similar generic algorithms that can zoom in on a particular enum case.\n\n## Introducing: case paths\n\nThis library intends to bridge this gap by introducing what we call \"case paths.\" Case paths can be constructed simply by prepending the enum type and case name with a _forward_ slash:\n\n``` swift\nimport CasePaths\n\n/Authentication.authenticated // CasePath\u003cAuthentication, String\u003e\n```\n\n### Case paths vs. key paths\n\nWhile key paths package up the functionality of getting and setting a value on a root structure, case paths package up the functionality of extracting and embedding a value on a root enumeration.\n\n``` swift\nuser[keyPath: \\User.id] = 42\nuser[keyPath: \\User.id] // 42\n\nlet authentication = (/Authentication.authenticated).embed(\"cafebeef\")\n(/Authentication.authenticated).extract(from: authentication) // Optional(\"cafebeef\")\n```\n\nCase path extraction can fail and return `nil` because the cases may not match up.\n\n``` swift\n(/Authentication.authenticated).extract(from: .unauthenticated) // nil\n````\n\nCase paths, like key paths, compose. Where key paths use dot-syntax to dive deeper into a structure, case paths use a double-dot syntax:\n\n``` swift\n\\HighScore.user.name\n// WritableKeyPath\u003cHighScore, String\u003e\n\n/Result\u003cAuthentication, Error\u003e..Authentication.authenticated\n// CasePath\u003cResult\u003cAuthentication, Error\u003e, String\u003e\n```\n\nCase paths, also like key paths, provide an \"[identity](https://github.com/apple/swift-evolution/blob/master/proposals/0227-identity-keypath.md)\" path, which is useful for interacting with APIs that use key paths and case paths but you want to work with entire structure.\n\n``` swift\n\\User.self           // WritableKeyPath\u003cUser, User\u003e\n/Authentication.self // CasePath\u003cAuthentication, Authentication\u003e\n```\n\nKey paths are created for every property, even computed ones, so what is the equivalent for case paths? Well, \"computed\" case paths can be created by providing custom `embed` and `extract` functions:\n\n``` swift\nCasePath\u003cAuthentication, String\u003e(\n  embed: { decryptedToken in\n    Authentication.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\nSince Swift 5.2, key path expressions can be passed directly to methods like `map`. The same is true of case path expressions, which can be passed to methods like `compactMap`:\n\n``` swift\nusers.map(\\User.name)\nauthentications.compactMap(/Authentication.authenticated)\n```\n\n## Ergonomic associated value access\n\nCasePaths 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:\n\n``` swift\nextract(case: Authentication.authenticated, from: .authenticated(\"cafebeef\"))\n// Optional(\"cafebeef\")\n```\n\n## Case paths without operators\n\nThe operators included with CasePaths make working with case paths feel a lot like working with key paths, but if your team or code base is operator-averse, they are not required.\n\n``` swift\n// With operators:\n/Authentication.authenticated\n// Without:\nCasePath.case(Authentication.authenticated)\n\n// With operators:\nauthentications.compactMap(/Authentication.authenticated)\n// Without:\nauthentications.compactMap(extract(Authentication.authenticated))\n\n// With operators:\n/Result\u003cAuthentication, Error\u003e.success..Authentication.authenticated\n// Without:\nCasePath.case(Result\u003cAuthentication, Error\u003e.success)\n  .appending(path: .case(Authentication.authenticated))\n\n// With operators:\n/Authentication.self\n// Without operators:\nCasePath\u003cAuthentication, Authentication\u003e.self\n```\n\n## Installation\n\nYou can add CasePaths to an Xcode project by adding it as a package dependency.\n\n\u003e https://github.com/pointfreeco/swift-case-paths\n\nIf you want to use CasePaths in a [SwiftPM](https://swift.org/package-manager/) project, it's as simple as adding a `dependencies` clause to your `Package.swift`:\n\n``` swift\ndependencies: [\n  .package(url: \"https://github.com/pointfreeco/swift-case-paths.git\", from: \"0.1.0\")\n]\n```\n\n## Prior art\n\n  - [`EnumKit`](https://github.com/gringoireDM/EnumKit) is a protocol-oriented, reflection-based solution to ergonomic enum access and inspired the creation of this library.\n\n## Interested in learning more?\n\nThese concepts (and more) are explored thoroughly in [Point-Free](https://www.pointfree.co), a video series exploring functional programming and Swift hosted by [Brandon Williams](https://github.com/mbrandonw) and [Stephen Celis](https://github.com/stephencelis).\n\nThe design of this library was explored in the following [Point-Free](https://www.pointfree.co) episodes:\n\n  - [Episode 87](https://www.pointfree.co/episodes/ep87-the-case-for-case-paths-introduction): The Case for Case Paths: Introduction\n  - [Episode 88](https://www.pointfree.co/episodes/ep88-the-case-for-case-paths-properties): The 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://i.vimeocdn.com/video/848203050.jpg\" 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%2Fstephencelis%2Fscptest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstephencelis%2Fscptest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstephencelis%2Fscptest/lists"}