{"id":13496276,"url":"https://github.com/pointfreeco/swift-validated","last_synced_at":"2025-04-05T04:14:46.502Z","repository":{"id":47670962,"uuid":"145148331","full_name":"pointfreeco/swift-validated","owner":"pointfreeco","description":"🛂 A result type that accumulates multiple errors.","archived":false,"fork":false,"pushed_at":"2024-07-05T17:47:00.000Z","size":63,"stargazers_count":392,"open_issues_count":0,"forks_count":19,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-29T03:09:49.868Z","etag":null,"topics":["error-handling","functional-programming","result-type","validation"],"latest_commit_sha":null,"homepage":"https://www.pointfree.co/episodes/ep24-the-many-faces-of-zip-part-2","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}},"created_at":"2018-08-17T17:24:55.000Z","updated_at":"2025-02-17T10:57:19.000Z","dependencies_parsed_at":"2024-11-03T09:16:15.506Z","dependency_job_id":null,"html_url":"https://github.com/pointfreeco/swift-validated","commit_stats":{"total_commits":35,"total_committers":4,"mean_commits":8.75,"dds":"0.11428571428571432","last_synced_commit":"63a96d90c5b8d91f0affdeeeb3fb124b83af66d0"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-validated","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-validated/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-validated/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pointfreeco%2Fswift-validated/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pointfreeco","download_url":"https://codeload.github.com/pointfreeco/swift-validated/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247284954,"owners_count":20913704,"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":["error-handling","functional-programming","result-type","validation"],"created_at":"2024-07-31T19:01:44.935Z","updated_at":"2025-04-05T04:14:46.470Z","avatar_url":"https://github.com/pointfreeco.png","language":"Swift","funding_links":[],"categories":["Swift","HarmonyOS"],"sub_categories":["Windows Manager"],"readme":"# 🛂 Validated\n\n[![CI](https://github.com/pointfreeco/swift-validated/workflows/CI/badge.svg)](https://actions-badge.atrox.dev/pointfreeco/swift-validated/goto)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpointfreeco%2Fswift-validated%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pointfreeco/swift-validated)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpointfreeco%2Fswift-validated%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pointfreeco/swift-validated)\n\nA result type that accumulates multiple errors.\n\n## Table of Contents\n\n  - [Motivation](#motivation)\n      - [The problem](#the-problem)\n      - [Handling multiple errors with Validated](#handling-multiple-errors-with-validated)\n  - [Installation](#installation)\n  - [Interested in learning more?](#interested-in-learning-more)\n  - [License](#license)\n\n## Motivation\n\n### The problem\n\nSwift error handling short-circuits on the first failure. Because of this, it's not the greatest option for handling things like form data, where multiple inputs may result in multiple errors.\n\n``` swift\nstruct User {\n  let id: Int\n  let email: String\n  let name: String\n}\n\nfunc validate(id: Int) throws -\u003e Int {\n  guard id \u003e 0 else {\n    throw Invalid.error(\"id must be greater than zero\")\n  }\n  return id\n}\n\nfunc validate(email: String) throws -\u003e String {\n  guard email.contains(\"@\") else {\n    throw Invalid.error(\"email must be valid\")\n  }\n  return email\n}\n\nfunc validate(name: String) throws -\u003e String {\n  guard !name.isEmpty else {\n    throw Invalid.error(\"name can't be blank\")\n  }\n  return name\n}\n\nfunc validateUser(id: Int, email: String, name: String) throws -\u003e User {\n  return User(\n    id: try validate(id: id),\n    email: try validate(id: email),\n    name: try validate(id: name)\n  )\n}\n```\n\nHere we've combined a few throwing functions into a single throwing function that may return a `User`.\n\n``` swift\nlet user = try validateUser(id: 1, email: \"blob@pointfree.co\", name: \"Blob\")\n// User(id: 1, email: \"blob@pointfree.co\", name: \"Blob\")\n```\n\nIf the `id`, `email`, or `name` are invalid, an error is thrown.\n\n``` swift\nlet user = try validateUser(id: 1, email: \"blob@pointfree.co\", name: \"\")\n// throws Invalid.error(\"name can't be blank\")\n```\n\nUnfortunately, if several or all of these inputs are invalid, the first error wins.\n\n``` swift\nlet user = try validateUser(id: -1, email: \"blobpointfree.co\", name: \"\")\n// throws Invalid.error(\"id must be greater than zero\")\n```\n\n### Handling multiple errors with Validated\n\n`Validated` is a [`Result`](https://github.com/antitypical/Result)-like type that can accumulate multiple errors. Instead of using `throw`ing functions, we can define functions that work with `Validated`.\n\n``` swift\nfunc validate(id: Int) -\u003e Validated\u003cInt, String\u003e {\n  return id \u003e 0\n    ? .valid(id)\n    : .error(\"id must be greater than zero\")\n}\n\nfunc validate(email: String) -\u003e Validated\u003cString, String\u003e {\n  return email.contains(\"@\")\n    ? .valid(email)\n    : .error(\"email must be valid\")\n}\n\nfunc validate(name: String) -\u003e Validated\u003cString, String\u003e {\n  return !name.isEmpty\n    ? .valid(name)\n    : .error(\"name can't be blank\")\n}\n```\n\nTo accumulate errors, we use a function that we may already be familiar with: `zip`. \n\n``` swift\nlet validInputs = zip(\n  validate(id: 1),\n  validate(email: \"blob@pointfree.co\"),\n  validate(name: \"Blob\")\n)\n// Validated\u003c(Int, String, String), String\u003e\n```\n\nThe `zip` function on `Validated` works much the same way it works on sequences, but rather than zipping a pair of sequences into a sequence of pairs, it zips up a group of single `Validated` values into single `Validated` value of a group.\n\nFrom here, we can use another function that we may already be familiar with, `map`, which takes a transform function and produces a new `Validated` value with its valid case transformed.\n\n``` swift\nlet validUser = validInputs.map(User.init)\n// valid(User(id: 1, email: \"blob@pointfree.co\", name: \"Blob\"))\n```\n\nOut group of valid inputs has transformed into a valid user.\n\nFor ergonomics and composition, a curried `zip(with:)` function is provided that takes both a transform function and `Validated` inputs.\n\n``` swift\nzip(with: User.init)(\n  validate(id: 1),\n  validate(email: \"blob@pointfree.co\"),\n  validate(name: \"Blob\")\n)\n// valid(User(id: 1, email: \"blob@pointfree.co\", name: \"Blob\"))\n```\n\nAn invalid input yields an error in the `invalid` case.\n\n``` swift\nzip(with: User.init)(\n  validate(id: 1),\n  validate(email: \"blob@pointfree.co\"),\n  validate(name: \"\")\n)\n// invalid([\"name can't be blank\"])\n```\n\nMore importantly, multiple invalid inputs yield an `invalid` case with multiple errors.\n\n``` swift\nzip(with: User.init)(\n  validate(id: -1),\n  validate(email: \"blobpointfree.co\"),\n  validate(name: \"\")\n)\n// invalid([\n//   \"id must be greater than zero\",\n//   \"email must be valid\",\n//   \"name can't be blank\"\n// ])\n```\n\nInvalid errors are held in a [non-empty array](https://github.com/pointfreeco/swift-nonempty.git) to provide a compile-time guarantee that you will never encounter an empty `invalid` case.\n\n## Installation\n\nYou can add Validated to an Xcode project by adding it as a package dependency.\n\n\u003e https://github.com/pointfreeco/swift-validated\n\nIf you want to use Validated in a [SwiftPM](https://swift.org/package-manager/) project, it's as simple as adding it to a `dependencies` clause in your `Package.swift`:\n\n``` swift\ndependencies: [\n  .package(url: \"https://github.com/pointfreeco/swift-validated\", from: \"0.2.1\")\n]\n```\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\nValidated was explored in [The Many Faces of Zip: Part 2](https://www.pointfree.co/episodes/ep24-the-many-faces-of-zip-part-2):\n\n\u003ca href=\"https://www.pointfree.co/episodes/ep24-the-many-faces-of-zip-part-2\"\u003e\n  \u003cimg alt=\"video poster image\" src=\"https://d3rccdn33rt8ze.cloudfront.net/episodes/0024.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-validated","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpointfreeco%2Fswift-validated","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpointfreeco%2Fswift-validated/lists"}