• Stars
    star
    389
  • Rank 106,543 (Top 3 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 5 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿ›‚ A result type that accumulates multiple errors.

๐Ÿ›‚ Validated

CI

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) 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.

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 throwing 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: "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 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 project, it's as simple as adding it to a dependencies clause in your Package.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, 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:

video poster image

License

All modules are released under the MIT license. See LICENSE for details.

More Repositories

1

swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
Swift
10,990
star
2

swift-snapshot-testing

๐Ÿ“ธ Delightful Swift snapshot testing.
Swift
3,559
star
3

isowords

Open source game built in SwiftUI and the Composable Architecture.
Swift
2,515
star
4

swiftui-navigation

Tools for making SwiftUI navigation simpler, more ergonomic and more precise.
Swift
1,654
star
5

swift-dependencies

A dependency management library inspired by SwiftUI's "environment."
Swift
1,322
star
6

swift-tagged

๐Ÿท A wrapper type for safer, expressive code.
Swift
1,289
star
7

swift-overture

๐ŸŽผ A library for function composition.
Swift
1,115
star
8

pointfreeco

๐ŸŽฌ The source for www.pointfree.co, a video series on functional programming and the Swift programming language.
Swift
1,054
star
9

episode-code-samples

๐Ÿ’พ Point-Free episode code.
Swift
922
star
10

swift-case-paths

๐Ÿงฐ Case paths extends the key path hierarchy to enum cases.
Swift
852
star
11

swift-nonempty

๐ŸŽ A compile-time guarantee that a collection contains a value.
Swift
817
star
12

swift-parsing

A library for turning nebulous data into well-structured data, with a focus on composition, performance, generality, and ergonomics.
Swift
802
star
13

swift-custom-dump

A collection of tools for debugging, diffing, and testing your application's data structures.
Swift
766
star
14

swift-html

๐Ÿ—บ A Swift DSL for type-safe, extensible, and transformable HTML documents.
Swift
723
star
15

combine-schedulers

โฐ A few schedulers that make working with Combine more testable and more versatile.
Swift
671
star
16

swift-web

๐Ÿ•ธ A collection of Swift server-side frameworks for handling HTML, CSS, routing and middleware.
Swift
477
star
17

swift-identified-collections

A library of data structures for working with collections of identifiable elements in an ergonomic, performant way.
Swift
476
star
18

swift-prelude

๐ŸŽถ A collection of types and functions that enhance the Swift language.
Swift
456
star
19

swift-perception

Observable tools, backported.
Swift
399
star
20

swift-url-routing

A bidirectional router with more type safety and less fuss.
Swift
321
star
21

swift-concurrency-extras

Useful, testable Swift concurrency.
Swift
267
star
22

swift-gen

๐ŸŽฑ Composable, transformable, controllable randomness.
Swift
261
star
23

swift-clocks

โฐ A few clocks that make working with Swift concurrency more testable and more versatile.
Swift
229
star
24

swift-enum-properties

๐Ÿค Struct and enum data access in harmony.
Swift
198
star
25

xctest-dynamic-overlay

Define XCTest assertion helpers directly in your application and library code.
Swift
197
star
26

swift-macro-testing

Magical testing tools for Swift macros.
Swift
192
star
27

syncups

A rebuild of Appleโ€™s โ€œScrumdingerโ€ application using modern, best practices for SwiftUI development.
Swift
153
star
28

composable-core-location

A library that bridges the Composable Architecture and Core Location.
Swift
100
star
29

vapor-routing

A bidirectional Vapor router with more type safety and less fuss.
Swift
84
star
30

swift-html-vapor

๐Ÿ’ง Vapor plugin for type-safe, transformable HTML views.
Swift
82
star
31

swift-playground-templates

๐Ÿซ A collection of helpful Xcode playground templates.
Makefile
80
star
32

pointfreeco-server

Point-Free server code.
39
star
33

swift-boundaries

๐Ÿฃ Functional core, imperative shell.
Swift
27
star
34

composable-core-motion

A library that bridges the Composable Architecture and Core Motion.
Swift
26
star
35

swift-quickcheck

๐Ÿ An implementation of QuickCheck in Swift.
Swift
24
star
36

swift-algebras

Algebraic laws bundled into concrete data types.
19
star
37

swift-either

For those times you want A or B!
Swift
19
star
38

swift-parser-printer

โ†”๏ธ Parsing and printing
Swift
14
star
39

swift-html-kitura

โ˜๏ธ Kitura plugin for type-safe, transformable HTML views.
Swift
13
star
40

homebrew-swift

Ruby
2
star
41

swift-bugs

2
star
42

Ccmark

Swift
1
star