When writing my first Clojure application, I came across a very common problem, validating user input. Even though there are a few libraries out there that do this already (valip, validateur, corroborate), I needed something that could handle a broader range of use cases. So, I built a library called Metis (pronounced mee'-tis).
Metis draws heavily from the features and terminology provided by Active Record Validations. Metis, however, is not coupled to any sort of persistence or presentation. It simply validates data. Let's take a look at what it can do.
A simple example
Metis is designed to validate maps of data. To show some simple functionality, let's validate some data that one might get from a HTML form when registering for a website. The example form accepts a user's email, password, and password confirmation.
defvalidator dsl, we can assign one or more validators to a key in the map. In this example, the
:email-address key and the
:confirmation validators are assigned to the
defvalidator produces a function,
reg-validator, which can now be used to validate maps. Let's run it and see what the output looks like.
If we give it invalid data, we are returned a map of errors. As you can see, each invalid key will have a collection of errors. In this case, there is only one error for both
:password. Let's try again with some more invalid data.
:password key and
:password-confirm key no longer match (before they were both
nil), we have tripped the
:confirmation validator and produced another error.
If we give it valid data, the errors map will be empty.
Now that we have the basics down, let's move on to some cooler features.
Defining custom validators
Even though Metis has many built-in validators, you will probably need to define your own at some point. Custom validators are defined in the same way that the built-in validators are defined, as functions.
A validator is simply a function that takes in a map and returns an error or nil. As an example, let's look at the built-in presence validator.
As you can see, this is a very simple validator. It checks if the value is present and returns an error if it is not. This is the structure of all the validators in Metis. Every validator takes in the map, the key to be validated, and a map of options. The presence validator, however, does not take in any options, so the third option is ignored.
Lets define a custom validator that checks if every charater is an 'a'.
As I said before, validators are functions that accept a map, key and options. The function produced by the
defvalidator macro also adheres to this interface, meaning that it can be reused in the same manner as custom validators. Let's take a look at how we can use this simple feature to validate nested maps.
Often times, the set of validations to run is not cut and dry. Consider a payment form in which the user can opt to input their credit card number or PayPal information. If they select credit card, we have to validate that the credit card number is formatted correctly. If they select PayPal, we have to validate the email address.
This can be accomplished using the
:if-not options. The
:if option is used to specify when the validation should happen. The
:if-not option is used to specify when the validation should not happen.
Often times, a set of data, say a user's profile, will have multiple forms in an application; one form for creating the profile and another for updating. It can be useful to share the same validations across both of these forms, especially if there are many shared validations between them. However, there is always going to be some pesky field that is required for one form and not the other. To solve this, we can use contexts. The
:only option is used to specify the contexts in which the validation should be run. The
:except option is used to specify the contexts from which the validation should be excluded.
Note: the context names here are arbitrary; they can be anything.
There you have it! Metis is a library designed to take maps and validate their structure and content. To see more examples and view the full API, check out the README on the project home page. To see a list of all the built-in validators, check out the wiki. Enjoy!