My recent (2020?!) post introduced an idea for lightweight validation in swift that can gather all the errors that failed the validation.
This post is a continuation where I implement a result builder for this validation so that it would look a bit cleaner:
1 2 3 4
Result builders are a new feature in swift 5.4 (which is in Xcode 12.5 by default). Xcode 12.4 doesn’t have it, so if you use it, you can download the official swift 5.4 compiler from https://swift.org/download/: https://swift.org/builds/swift-5.4.1-release/xcode/swift-5.4.1-RELEASE/swift-5.4.1-RELEASE-osx.pkg. However the built tests don’t run on OSX 10.14:
Internal error, you see.
The implementation is available in the
feature/result_builders branch of the
LightweightValidation repository. Tests are also added in every commit.
A result builder is a
struct with the
@resultBuilder attribute that implements one or more of the required functions for different syntax cases. The first version only contains
buildBlock(_:...) that’s used to build a result from 1+ validations:
1 2 3 4 5 6
This is the simplest case where we return the first validation (a new test verifies that). To use this builder you need to have a function that accepts a closure containing the validation steps:
1 2 3 4 5
As a reminder,
V is the validation type we’re working with from the first blogpost. This
all function is the API for our users:
1 2 3
The next logical step is to finish the implementation so that we can actually use:
1 2 3 4 5
in addition to:
As I understand,
validations can’t be empty in that
buildBlock because there is another signature for the case when there is nothing in the result builder and I haven’t implemented that one, so the implementation was:
1 2 3 4 5
which I changed to a safer one:
1 2 3 4 5
This is where we
&& all the supplied validations, returning the same result as if we manually
V.empty = V.value(()) is an identity that can be combined with any validation and not change it.
1 2 3 4 5 6 7 8 9 10 11
And now we can update
Response.validateUserName to this:
1 2 3 4 5 6 7 8 9
How much cleaner is the resulting syntax? With the simple example, not much honestly. But since you can also support conditions and loops right in the closures, it probably becomes more straightforward then.
To learn more:
this article is a good guide when starting to support this feature: https://www.swiftbysundell.com/articles/deep-dive-into-swift-function-builders/;
the official documentation is in the swift book: https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html#ID630.
I still can’t shake the feeling that adding these syntax sugars to the compiler is a limiting approach because no library can do the same. For example,
throws added error reporting to functions, but it’s built into language and has shortcomings vs creating a
Either) that can be done by any library w/o touching the compiler.