|
| 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