Skip to content

Commit e469456

Browse files
committed
form validation
1 parent c2a6cba commit e469456

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
+++
2+
title = "Form validation"
3+
weight = 140
4+
+++
5+
6+
We can recommend the [clavier](https://github.com/mmontone/clavier/)
7+
library for input validation.
8+
9+
See also the [cl-forms](https://github.com/mmontone/cl-forms) library
10+
that offers many features:
11+
12+
- automatic forms
13+
- form validation with in-line error messages
14+
- client-side validation
15+
- subforms
16+
- Djula and Spinneret renderers
17+
- default themes
18+
- an online demo for you to try
19+
- etc
20+
21+
Get them:
22+
23+
```lisp
24+
(ql:quickload '(:clavier :cl-forms))
25+
```
26+
27+
## Form validation with the Clavier library
28+
29+
Clavier defines validators as class instances. They come in many
30+
types, for example the `'less-than`,
31+
`greater-than` or `len` validators. They may
32+
take an initialization argument.
33+
34+
We create them with either `make-instance` + the class name, either with their shortcut:
35+
36+
```lisp
37+
(make-instance 'clavier:less-than-validator :number 10)
38+
;; OR
39+
(clavier:less-than 10)
40+
41+
;; both return =>
42+
#<CLAVIER:LESS-THAN-VALIDATOR {10066FF47B}>
43+
```
44+
45+
Then, validate them by calling `clavier:validate` with the value to validate:
46+
47+
```lisp
48+
(clavier:validate * 9)
49+
;; =>
50+
T
51+
NIL
52+
```
53+
54+
It return two values: the status, an optional error message.
55+
56+
```lisp
57+
(clavier:validate ** 11)
58+
;; =>
59+
NIL
60+
"11 is not lower than 10"
61+
```
62+
63+
It's also possible to just `funcall` the validator objects (they are
64+
funcallable classes).
65+
66+
### Composing validators
67+
68+
You can compose them with boolean logic, using clavier's `||`, `&&` operators:
69+
70+
```lisp
71+
(defparameter *validator* (clavier:||
72+
(clavier:blank)
73+
(clavier:&& (clavier:is-a-string)
74+
(clavier:len :min 10)))
75+
"Allow a blank value. When non blank, validate.")
76+
```
77+
78+
This validator allows an input to be an empty string, but if it isn't, it validates it.
79+
80+
```lisp
81+
(funcall *validator* "")
82+
;; =>
83+
T
84+
NIL
85+
86+
(funcall *validator* "foo")
87+
;; =>
88+
NIL
89+
"Length of \"asdf\" is less than 10"
90+
```
91+
92+
## List of validators
93+
94+
This is the list of available validator classes and their shortcut function.
95+
96+
Some take an initialization argument. Look at your editor's tooltip for the function signature.
97+
98+
* equal-to-validator `(==)`
99+
* not-equal-to-validator `(~=)`
100+
* blank-validator `(blank)`
101+
* not-blank-validator `(not-blank)`
102+
* true-validator `(is-true)`
103+
* false-validator `(is-false)`
104+
* type-validator `(is-a type)`
105+
* string-validator `(is-a-string)`
106+
* boolean-validator `(is-a-boolean)`
107+
* integer-validator `(is-an-integer)`
108+
* symbol-validator `(is-a-symbol)`
109+
* keyword-validator `(is-a-keyword)`
110+
* list-validator `(is-a-list)`
111+
* function-validator `(fn function message)`
112+
* email-validator `(valid-email)`
113+
* regex-validator `(matches-regex)`
114+
* url-validator `(valid-url)`
115+
* datetime-validator `(valid-datetime)`
116+
* pathname-validator `(valid-pathname)`
117+
* not-validator `(~ validator)`
118+
* and-validator `(&& validator1 validator2)`
119+
* or-validator `(|| validator1 validator2)`
120+
* one-of-validator `(one-of options)`
121+
* less-than-validator `(less-than number)`
122+
* greater-than-validator `(greater-than number)`
123+
* length-validator `(len)`
124+
* `:allow-blank` (not merged, only in my fork)
125+
126+
### Validation utils
127+
128+
We share some functions we used to complement Clavier or make it more convenient.
129+
130+
For example we wanted a shorter construct for this common need seen
131+
above of allowing empty strings.
132+
133+
We also want a function to validate a list of validators and collect
134+
the error messages.
135+
136+
We define a `validate-all` function that takes a list of
137+
validators and validates them in turn on `object`.
138+
139+
It recognizes a `:allow-blank` keyword.
140+
141+
```lisp
142+
(defun validate-all (validators object)
143+
"Run all validators in turn. Return two values: the status (boolean), and a list of messages.
144+
145+
Allow a keyword validator: :allow-blank. Accepts a blank value. If not blank, validate."
146+
;; I wanted this to be part of clavier, but well.
147+
;; https://github.com/mmontone/clavier/pull/10
148+
(let ((messages nil)
149+
(valid t))
150+
(loop for validator in validators
151+
if (and (eql :allow-blank validator)
152+
(str:blankp object))
153+
return t
154+
else
155+
do (unless (symbolp validator)
156+
(multiple-value-bind (status message)
157+
(clavier:validate validator object :error-p nil)
158+
(unless status
159+
(setf valid nil))
160+
(when message
161+
(push message messages)))))
162+
(values valid
163+
(reverse (uiop:ensure-list messages)))))
164+
```
165+
166+
Usage:
167+
168+
```lisp
169+
(validate-all (list :allow-blank (clavier:len :min 5)) "")
170+
T
171+
NIL
172+
173+
174+
(validate-all (list :allow-blank (clavier:len :min 5)) "foo")
175+
NIL
176+
("Length of \"foo\" is less than 5")
177+
```
178+
179+
Note that Clavier has a "validator-collection" thing, but not shown in
180+
the README, and is in our opininion too verbose in comparison to a
181+
simple list.

0 commit comments

Comments
 (0)