Form validation

We can recommend the clavier library for input validation.

See also the cl-forms library that offers many features:

  • automatic forms
  • form validation with in-line error messages
  • CSRF protection
  • client-side validation
  • subforms
  • Djula and Spinneret renderers
  • default themes
  • an online demo for you to try
  • etc

Get them:

(ql:quickload '(:clavier :cl-forms))

Form validation with the Clavier library

Clavier defines validators as class instances. They come in many types, for example the 'less-than, greater-than or len validators. They may take an initialization argument.

We create them with either make-instance + the class name, either with their shortcut:

(make-instance 'clavier:less-than-validator :number 10)
;; OR
(clavier:less-than 10)

;; both return =>
#<CLAVIER:LESS-THAN-VALIDATOR {10066FF47B}>

Then, validate them by calling clavier:validate with the value to validate:

(clavier:validate * 9)
;; =>
T
NIL

It return two values: the status, an optional error message.

(clavier:validate ** 11)
;; =>
NIL
"11 is not lower than 10"

It’s also possible to just funcall the validator objects (they are funcallable classes).

Composing validators

You can compose them with boolean logic, using clavier’s ||, && operators:

(defparameter *validator* (clavier:||
                                   (clavier:blank)
                                   (clavier:&& (clavier:is-a-string)
                                               (clavier:len :min 10)))
  "Allow a blank value. When non blank, validate.")

This validator allows an input to be an empty string, but if it isn’t, it validates it.

(funcall *validator* "")
;; =>
T
NIL

(funcall *validator* "foo")
;; =>
NIL
"Length of \"foo\" is less than 10"

List of validators

This is the list of available validator classes and their shortcut function.

Some take an initialization argument. Look at your editor’s tooltip for the function signature.

  • equal-to-validator (==)
  • not-equal-to-validator (~=)
  • blank-validator (blank)
  • not-blank-validator (not-blank)
  • true-validator (is-true)
  • false-validator (is-false)
  • type-validator (is-a type)
  • string-validator (is-a-string)
  • boolean-validator (is-a-boolean)
  • integer-validator (is-an-integer)
  • symbol-validator (is-a-symbol)
  • keyword-validator (is-a-keyword)
  • list-validator (is-a-list)
  • function-validator (fn function message)
  • email-validator (valid-email)
  • regex-validator (matches-regex)
  • url-validator (valid-url)
  • datetime-validator (valid-datetime)
  • pathname-validator (valid-pathname)
  • not-validator (~ validator)
  • and-validator (&& validator1 validator2)
  • or-validator (|| validator1 validator2)
  • one-of-validator (one-of options)
  • less-than-validator (less-than number)
  • greater-than-validator (greater-than number)
  • length-validator (len)
  • :allow-blank (not merged, only in my fork)

Validation utils

We share some functions we used to complement Clavier or make it more convenient.

For example we wanted a shorter construct for this common need seen above of allowing empty strings.

We also want a function to validate a list of validators and collect the error messages.

We define a validate-all function that takes a list of validators and validates them in turn on object.

It recognizes a :allow-blank keyword.

(defun validate-all (validators object)
  "Run all validators in turn. Return two values: the status (boolean), and a list of messages.

  Allow a keyword validator: :allow-blank. Accepts a blank value. If not blank, validate."
  ;; I wanted this to be part of clavier, but well.
  ;; https://github.com/mmontone/clavier/pull/10
  (let ((messages nil)
        (valid t))
    (loop for validator in validators
          if (and (eql :allow-blank validator)
                  (str:blankp object))
            return t
          else
            do (unless (symbolp validator)
                 (multiple-value-bind (status message)
                     (clavier:validate validator object :error-p nil)
                   (unless status
                     (setf valid nil))
                   (when message
                     (push message messages)))))
    (values valid
            (reverse (uiop:ensure-list messages)))))

Usage:

(validate-all (list :allow-blank (clavier:len :min 5)) "")
T
NIL


(validate-all (list :allow-blank (clavier:len :min 5)) "foo")
NIL
("Length of \"foo\" is less than 5")

Note that Clavier has a “validator-collection” thing, but not shown in the README, and is in our opininion too verbose in comparison to a simple list.