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)
Top comments (0)