Composing Validations

I am currently working on a library for creating REST clients. It has three main goals; to be type-safe, be un-opinionated about serialisation/de-serialisation, and support any HTTP client with minimal work.

As part of this project, I’ve started to think about how the resulting clients would be used to interact with remote APIs. One requirement is the need to validate user input before making any requests.

My goals

  • Compose arbitrary validations
  • Have a library of built-in validators
  • Where appropriate, make validators generic over types e.g. Collection
  • Describe and categorise failures rather than just returning a Bool
  • Allow validators to be configurable e.g. “collection must be of length N”
  • Allow localised validation messages

The First Attempt

My initial experiments used types and generics in order to implement boolean operators — similar to Ben-G’s Validated library. Using this method involves creating types for the validators and their composition i.e. and, or, not. For example, here is how we might define a validator that requires two checks to pass.

typealias ValidString = Validate<String, And<Required, MaxLength200>>

This is quite nice and quite clever. I mean, who doesn’t like type-level shenanigans? I did however start to find this approach a bit awkward, especially when attempting to define a more complicated predicate. Consider the following check on a string:

The string must start with “x” and be less than two hundred characters or start with “y” and be greater than fifty characters.

This turns out to be quite difficult to express with types. Let’s try it.

typealias Crazy = Validate<String, Or<And<StartsWithX, MaxLength200>, And<StartsWithY, GreaterThan50>>>

Now that’s a pretty simple requirement, but the type signature is too much. It also has one major limitation, which is that any validations that rely on a value — for example “starts with x” — that value must be encoded in the type. This means each time you need a validation that relies on a different value, you have to define a new type.

There are further troubles. How about the following?

Starts with “x” and ends with “y” and is ten characters long

Let’s try to do it with types.

typealias V = Validate<String, And<And<StartsWithX, EndsWithY>, Length10>>

That’s getting awkward. Again, consider that even though this is a simple predicate, the type is quite difficult to scan.

Types are Fun, but Functions are Forever

Let’s step back a bit. Using types to encode validators is clever, but it isn’t buying us much. Remembering the list of goals I stated, my first requirement is the ability to compose validators. What’s something that composes? Functions!

Let’s try treating our validators as functions, beginning with a typealias.

typealias Validator<T> = (T?) -> Bool

Here is a simple example of checking that a value is non-optional.

func required<A>(_ value: A?) -> Bool {
    return value == nil
}

Now for the composition. The composition of two validators should be another validator. Since our validators are just plain functions, this is simple! We can add a function which takes two validator functions and returns a new validator. Just to be clever, let’s implement it as an operator.

func &&<A>(lhs: @escaping Validator<A>, rhs: @escaping Validator<A>) -> Validator<A> {
    return { value in
      return lhs(value) && rhs(value)
    }
}

So this gives us the and-operator from boolean logic. How about the or-operator?

func ||<A>(lhs: @escaping Validator<A>, rhs: @escaping Validator<A>) -> Validator<A> {
  return { value in
    return lhs(value) || rhs(value)
  }
}

So now we can go out and begin defining combinations of validators. Imagine we have a few other validators defined.

let validator = required && (length(10) || length(20))

Unfortunately, this won’t work. We’ve defined our validators so they are generic over the type of the value being checked. What is the type of the resulting validator? Unfortunately Swift won’t actually infer this one. We need to give it more context. Let’s add a struct to help us capture the type.

struct Validate<T> {
  let validator: Validator<T>
}

Now we can capture the type of the value and it will be correctly inferred for the validator functions.

let v = Validate<String?>(validator: required && (length(10) || length(20)))

I think that works quite well. As a test, let’s see what an implementation of one of the earlier validators would look like.

let v = Validate<String>(validator: startsWith("x") && endsWith("y") && length(10))

Personally, I find that much easier to read than a mess of types. It’s also pretty easy to see how the set of validator functions can be expanded.

The sketch outlined in this post doesn’t fulfil all my stated goals; I’ve elided discussion of the complete implementation for brevity. However, I have a more complete implementation available as a gist.

I found the process of working through this problem to be quite interesting. It has delivered a lesson which functional programmers are already aware of; often the only tools you need are functions.

◀︎ Return to homepage