GitHub - pointfreeco/swift-validated: ? A result type that accumulates multiple...
source link: https://github.com/pointfreeco/swift-validated
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
README.md
? Validated
A result type that accumulates multiple errors.
Table of Contents
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.
struct User { let id: Int let email: String let name: String } func validate(id: Int) -> Int throws { guard id > 0 else { throw Invalid.error("id must be greater than zero") } return id } func validate(email: String) -> String throws { guard email.contains("@") else { throw Invalid.error("email must be valid") } return email } func validate(name: String) -> String throws { 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
.
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.
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.
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
-like type that can accumulate multiple errors. Instead of using throw
ing functions, we can define functions that work with Validated
.
func validate(id: Int) -> Validated<Int, String> { return id > 0 ? .valid(id) : .error("id must be greater than zero") } func validate(email: String) -> Validated<String, String> { return email.contains("@") ? .valid(email) : .error("email must be valid") } func validate(name: String) -> Validated<String, String> { 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
.
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.
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.
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.
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.
zip(with: User.init)( validate(id: -1), validate(email: "[email protected]"), 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 to provide a compile-time guarantee that you will never encounter an empty invalid
case.
Installation
Carthage
If you use Carthage, you can add the following dependency to your Cartfile
:
github "pointfreeco/swift-validated" ~> 0.1
CocoaPods
If your project uses CocoaPods, just add the following to your Podfile
:
pod 'PointFree-Validated', '~> 0.1'
SwiftPM
If you want to use Validated in a project that uses SwiftPM, it's as simple as adding a dependencies
clause to your Package.swift
:
dependencies: [ .package(url: "https://github.com/pointfreeco/swift-validated.git", from: "0.1.0") ]
Xcode Sub-project
Submodule, clone, or download Validated, and drag Validated.xcodeproj
into your project.
Interested in learning more?
These concepts (and more) are explored thoroughly in Point-Free, a video series exploring functional programming and Swift hosted by Brandon Williams and Stephen Celis.
Validated was explored in The Many Faces of Zip: Part 2:
License
All modules are released under the MIT license. See LICENSE for details.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK