🎄 ValidationBlocks 🎄

This post is part of the English 2019 F# Advent Calendar, check out the other posts there, and special thanks to Sergey Tihon for organizing it.

Have you ever found yourself perusing Scott Wlaschin’s excellent Designing With Types series, and said to yourself “man, those single case unions are sweet”?

Have you ever then found yourself digging a little deeper only to realize you’d need a type declaration like the one below, for every… single… type?

// fsharpforfunandprofit.com

/// Type with constraint that value must be non-null
/// and <= 50 chars.
type String50 = private String50 of string

/// Module containing functions related to String50 type
module String50 =

    // NOTE: these functions can access the internals of the
    // type because they are in the same scope (namespace/module)

    /// constructor
    let create str = 
        if String.IsNullOrEmpty(str) then
            None
        elif String.length str > 50 then
            None
        else
            Some (String50 str)

    // function used to extract data since type is private
    let value (String50 str) = str

If this sounds familiar, you probably still decided to use a method like the one above, because the advantages of coding against types guaranteed-to-be-valid far outweigh the inconvenience of creating these type/module combinations.

But perhaps like me you’ve also been thinking that there must be a way to streamline this. If so, feel free to ignore the somewhat pointless backstory below and jump straight to the actual topic of the post.

Enter RealText

Six months ago, I set out to do just that, and I created a library to declare such types in as little lines of code as possible. I narrowed the scope of it to text (string) types because I figured that narrowing the scope would increase my chances of reducing boilerplate code. I created it, I’ve used it, I thought it was great, I thought one day I’d share it with the world, and I even gave it a name: RealText. The excitement was palpable… and short lived.

Exit RealText

With the infinite wisdom that comes with hindsight, I realized my library was mostly pointless. For one it still required some boilerplate code, which I kind of had to re-learn every time I had to create new types. Another problem was the fact that it only supported text types because it meant I now had two paradigms, one for text, and one for non-text types. So, without a second thought, I stashed away the whole idea in my increasingly crowded metaphorical closet of dead-end projects.

When at first you don’t succeed, go make babies

A few months later, after we had our second baby and during my paternity leave, I decided to re-tackle the problem with a fresh set of neurons. Learning from my mistakes, this time I decided to not limit any eventual solution to text types, and I also decided that the code necessary to define such types would be so small, that there would be no need to re-learn anything whenever it was necessary to define a new type.

Enter FSharp.ValidationBlocks

You know you’ve done something right when you can’t possibly imagine writing any less code to declare or implement whatever you have in mind, and that is exactly what I felt about the second implementation of RealText, now called FSharp.ValidationBlocks as it supports all primitive types, not just strings.

How it works

A quick word before delving in the details. Unlike Scott Wlaschin’s implementation above that relies on Option to differentiate between valid and invalid content, validation blocks are natively ROP-oriented, and failed validations return Error. One great advantage compared to the now-defunct RealText is that the type of error is generic, meaning you create your own validation errors as an existing or new DU in your domain, as you would for any other error, with whatever parameters you need to properly display meaningful error messages. Here’s an example defining three types:

  1. FreeText
     Any non-blank text (possibly multiline)
  2. Text
     Any non-blank text with no control characters
  3. Tweet
     Any non-blank text with no control characters and a maximum length of 280

Before we begin, we should also define some appropriate errors in our domain, here I’ve used a validation specific DU, but you don’t have to, there’s no constraints on Error type whatsoever.

type TextError =
    | IsBlank
    | ContainsControlCharacters
    | ExceedsMaximumLength of int

Let’s start with FreeText, the least restrictive type.

/// WARNING: Obsolete, refer to the GitHub project for current API
type FreeText = private FreeText of string with
    interface IText with
        member _.Validate =
            fun s ->
                [if System.String.IsNullOrWhitespace s
                	then IsBlank]

No doubt this declaration raises a couple of questions, but I think one thing that’s immediately obvious is that there’s hardly any superfluous code.

Validation is always a function of the primitive type, string in this case, that returns a list of errors under specific conditions. It always takes the following form, so writing these functions is a pretty mindless task.

fun x ->
    [
        if x |> test1 then Error1
        if x |> test2 then Error2
        if x |> test3 then Error3
        // ...
    ]

In other words, declaring types with validation blocks is reduced to saying “this is a validation block” (using the interface) and “under these conditions, you get these errors” (implementing the interface), which I believe we can all agree is the absolute minimum amount of code one can expect to write to define this behavior. It’s not just the type declaration that’s concise, creating a block can be as simple as calling Text.validate s, which returns a Result<'text, 'error>.

They are actually blocks

ValidationBlocks logo These validating types are meant to be built on top of each other, which explains the blocks part of the name. To see this in action, let’s continue implementing the remaining two types Text and Tweet from above.

/// Single line (no control chars) of non-blank text
type Text = private Text of FreeText with
    interface IText with
        member _.Validate =
            fun s ->
                [if s |> Regex("\p{C}").IsMatch
                    then ContainsControlCharacters]

Even though Text is defined as non-blank, we don’t explicitly write this validation, instead, we build on top of FreeText by declaring that Text is a Textof FreeText in the first line, the string will be automatically validated using the validation of FreeText before attempting the validation of Text. The rest of the type declaration is just a validation that yields an error if the string contains control characters, so now we’re ready to declare the last type.

/// Maximum 280 characters, non-blank, no control chars
type Tweet = private Tweet of Text with
    interface IText with
        member _.Validate =
            fun s ->
                [if s.Length > 280 then
                    ExceedsMaximumLength 280]

Again, we only declare the validation that’s specific to Tweet, all other validation rules are implied by writing Tweet of Text. The only new thing of interest here is the use of parameters in the error case which illustrates the ability to present more meaningful errors to the user for more complex validation types. I follow a strict discipline of having error union cases that together with their parameters (if any) allow me to generate an error message that spells out exactly what went wrong, so in this case I can very easily display a message “The given tweet exceeds the maximum length of 280 characters”, but I can also re-use the same error to display another message elsewhere that says “The given email exceeds the maximum length of 320 characters”.

Seriously, let’s talk Serialization

Your types may happily live within the boundaries of your domain as awesome validation blocks, but one day they probably have to leave your domain. I like to store my own blocks as their underlying primitive type, allowing me to refactor my code without breking anything, and most of the time your serialization needs are going to impose that anyway. In other words, your Tweet will have to be serialized as a string, not as a Tweet { Text { FreeText "covfefe" } }. For this reason, the library includes a System.Text.Json JsonConverter that does just that. Add it to your serialization options to ensure all your blocks serialize to their primitive type and deserialize back to the correct block type.

Final words

I’ve mostly covered the type declaration because for me that was the biggest disadvantage of the traditional way of designing with types. You want to declare types for anything that has specific validation needs so keeping these declarations compact is key, and when you think about it, almost any content that enters your domain can and probably should be validated.

But beyond type declaration, creating and using blocks of the declared types is easy, here’s how:

let tweet = Block.validate<Tweet> "hello!" // → Result<Tweet, TextError list>
Block.value tweet // → "hello!"

Note that while in a let binding you’d have to specify the generic parameter Tweet, in most cases it can be inferred and should be omitted:

Block.validate "hello!" // → Result<Tweet (inferred), TextError list>`

In the example file Text.fs you’ll find a Text module, it’s a good place to define both the TextError union as well as string-specific functionality. Here’s an example of a function to create Text blocks that converts empty strings into optional blocks, which is more convenient than handling missing text errors when using a block to populate an optional field:

/// This is a good place to define IText-specific functions
module Text

/// Validates the given string treating null/blank as a valid result of None
/// Use: Block.optional<Tweet> "hello!" or Block.optional "hello!"
let optional<'block when 'block :> IText> s : Result<'block option, TextError list> =
    if System.String.IsNullOrWhiteSpace s then Ok None
    else Block.validate<'block> s |> Result.map Some

Note that this module is named Text, but it’s generic (not specific to the Text type). I name my block that validates a single line of text Text because that’s the block I use the most and the brevity of the name “Text” keeps my declarations tidy, but if you don’t like that ambiguity you can always call it SingleTextLine for instance.

GitHub project & NuGet package

When I first published this article there was no GitHub project, but there’s one now, and that’s where you’ll find the most up-to-date information.

Package NuGet
FSharp.ValidationBlocks badge

Comments & Feedback

Feel free to share any comments or feedback by replying to this twitter post.

© 2020 Luis Ferrao — Powered by Jekyll & Poole