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)