Form validation in Common Lisp
vindarel
Posted on February 28, 2024
I've been using the clavier library for input validation, it works nicely but we could make it a bit more terse.
Let's say you are building many HTML forms. Doing one all manually is OK-ish, not two. You could use cl-forms (I didn't, I'm building a layer to get a form from Mito objects. If you didn't see where I'm doing it look better or stay tuned ;) ) You could do things semi-manually and use Clavier for input validation. It works like this.
Define a list of validators for your fields:
(defmethod validators ((obj (eql 'book)))
(dict 'isbn (list ;; other validator here…
(clavier:len :min 10 :max 13
;; :message works with clavier's commit of <2024-02-27>
;; :message "an ISBN must be between 10 and 13 characters long"
))
'title (clavier:~= "test"
"this title is too common, please change it!")))
You can compose them with boolean logic:
(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* "asdf")
;; =>
NIL
"Length of \"asdf\" is less than 10"
For one, I want a shorter construct for this common need. My PR was rejected so here it is.
Use a :allow-blank
keyword:
(defmethod validators ((obj (eql 'book)))
(dict 'isbn (list :allow-blank
(clavier:len :min 10 :max 13
…
and write a validate-all
function:
(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)))))
This could be made better for a library API maybe? Anyways it works for now©.
See also that Clavier has a "validator-collection" thing, but not shown in the README, and is again too verbose in comparison to a simple list, IMO.
that's it, see ya next time.
Appendix: validators list:
This is the list of available validator classes and their shortcut function:
- 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)
Posted on February 28, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.