Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/pointfreeco/swift-validated
🛂 A result type that accumulates multiple errors.
https://github.com/pointfreeco/swift-validated
error-handling functional-programming result-type validation
Last synced: 7 days ago
JSON representation
🛂 A result type that accumulates multiple errors.
- Host: GitHub
- URL: https://github.com/pointfreeco/swift-validated
- Owner: pointfreeco
- License: mit
- Created: 2018-08-17T17:24:55.000Z (about 6 years ago)
- Default Branch: main
- Last Pushed: 2024-07-05T17:47:00.000Z (4 months ago)
- Last Synced: 2024-10-31T11:37:01.588Z (14 days ago)
- Topics: error-handling, functional-programming, result-type, validation
- Language: Swift
- Homepage: https://www.pointfree.co/episodes/ep24-the-many-faces-of-zip-part-2
- Size: 61.5 KB
- Stars: 391
- Watchers: 8
- Forks: 19
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Code of conduct: .github/CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# 🛂 Validated
[![CI](https://github.com/pointfreeco/swift-validated/workflows/CI/badge.svg)](https://actions-badge.atrox.dev/pointfreeco/swift-validated/goto)
[![](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)
[![](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)A result type that accumulates multiple errors.
## Table of Contents
- [Motivation](#motivation)
- [The problem](#the-problem)
- [Handling multiple errors with Validated](#handling-multiple-errors-with-validated)
- [Installation](#installation)
- [Interested in learning more?](#interested-in-learning-more)
- [License](#license)## Motivation
### The problem
Swift 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.
``` swift
struct User {
let id: Int
let email: String
let name: String
}func validate(id: Int) throws -> Int {
guard id > 0 else {
throw Invalid.error("id must be greater than zero")
}
return id
}func validate(email: String) throws -> String {
guard email.contains("@") else {
throw Invalid.error("email must be valid")
}
return email
}func validate(name: String) throws -> String {
guard !name.isEmpty else {
throw Invalid.error("name can't be blank")
}
return name
}func validateUser(id: Int, email: String, name: String) throws -> User {
return User(
id: try validate(id: id),
email: try validate(id: email),
name: try validate(id: name)
)
}
```Here we've combined a few throwing functions into a single throwing function that may return a `User`.
``` swift
let user = try validateUser(id: 1, email: "[email protected]", name: "Blob")
// User(id: 1, email: "[email protected]", name: "Blob")
```If the `id`, `email`, or `name` are invalid, an error is thrown.
``` swift
let user = try validateUser(id: 1, email: "[email protected]", name: "")
// throws Invalid.error("name can't be blank")
```Unfortunately, if several or all of these inputs are invalid, the first error wins.
``` swift
let user = try validateUser(id: -1, email: "blobpointfree.co", name: "")
// throws Invalid.error("id must be greater than zero")
```### Handling multiple errors with Validated
`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`.
``` swift
func validate(id: Int) -> Validated {
return id > 0
? .valid(id)
: .error("id must be greater than zero")
}func validate(email: String) -> Validated {
return email.contains("@")
? .valid(email)
: .error("email must be valid")
}func validate(name: String) -> Validated {
return !name.isEmpty
? .valid(name)
: .error("name can't be blank")
}
```To accumulate errors, we use a function that we may already be familiar with: `zip`.
``` swift
let validInputs = zip(
validate(id: 1),
validate(email: "[email protected]"),
validate(name: "Blob")
)
// Validated<(Int, String, String), String>
```The `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.
From 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.
``` swift
let validUser = validInputs.map(User.init)
// valid(User(id: 1, email: "[email protected]", name: "Blob"))
```Out group of valid inputs has transformed into a valid user.
For ergonomics and composition, a curried `zip(with:)` function is provided that takes both a transform function and `Validated` inputs.
``` swift
zip(with: User.init)(
validate(id: 1),
validate(email: "[email protected]"),
validate(name: "Blob")
)
// valid(User(id: 1, email: "[email protected]", name: "Blob"))
```An invalid input yields an error in the `invalid` case.
``` swift
zip(with: User.init)(
validate(id: 1),
validate(email: "[email protected]"),
validate(name: "")
)
// invalid(["name can't be blank"])
```More importantly, multiple invalid inputs yield an `invalid` case with multiple errors.
``` swift
zip(with: User.init)(
validate(id: -1),
validate(email: "blobpointfree.co"),
validate(name: "")
)
// invalid([
// "id must be greater than zero",
// "email must be valid",
// "name can't be blank"
// ])
```Invalid 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.
## Installation
You can add Validated to an Xcode project by adding it as a package dependency.
> https://github.com/pointfreeco/swift-validated
If 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`:
``` swift
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-validated", from: "0.2.1")
]
```## Interested in learning more?
These 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).
Validated was explored in [The Many Faces of Zip: Part 2](https://www.pointfree.co/episodes/ep24-the-many-faces-of-zip-part-2):
## License
All modules are released under the MIT license. See [LICENSE](LICENSE) for details.