I’ve been reading my old essays and found this one written in Russian back in 2019.
Essentially, it describes a Ruby gem (library) I had written that allows you to create your own validation DSL as a bunch of functions and data. It emphises simplicity and flexibility. I thought it had a really interesting idea behind it so I’m going to rehash it here.
The below can be applied to any language (admittedly, it’ll be less convenient in some). I wrote examples in Ruby and Clojure.
Why I thought yet another validation DSL was a good idea
Modern validation libraries usually involve a lot of stuff. A lot of stuff. You need to learn a bunch of library functions, read docs, sometimes even a whole DSL. Also, they can be quite opinionated.
Now, to be completely honest with you, nowadays I don’t think it’s a bad thing. But it’s always nice to play around with ideas.
Fundamentals
The goals I was aiming for with that library were:
- Simplicity
- No magic
- Easy to learn
- Easy customisation with as little limitations as possible
Basically, I wanted to make it as simple as possible.
Architecture
I based the library on 4 concepts:
- Validator - object (could be a function) that validates data. Is built from a blueprint and transformations.
- Blueprint - data that describes data we want to validate. For instance,
:int
could represent an integer and{name: :string, email: :email, age: :positive}
could represent a user. - Transformation - a function
t(b, f)
whereb
is a blueprint andf
is a validator factory (details below) that returns either a validator or a blueprint. This is the core concept that I find interesting. Basically, a list of transformations describes your DSL. - Factory is built from a list of transformations. It’s a function (well, an object in the library) that takes a blueprint and passes it through all transformations (continuously!) until a validator is created. Factory gets stuck in an infinite loop, the schema is invalid (well, or the transformations). The gem isn’t handling this very well though - it merely checks if the blueprint stays the same after a full transformation cycle.
All that is enough to build your own validation DSL.
Factory implementation
Ruby
Clojure
DSL example
Ruby
Clojure
Conclusion
This is just a simple example (a prototype, if you will). Some benefits of approach like this:
- The core is minimal so it’s easy to learn
- Because each DSL is created with pure functions, they can easily be re-used
- It’s very portable as no special language features are required. Any functional or OOP language can have this.
My particular example simply returns true/false
but with some tuning it can
return explanations for validation failures.