First shot at a cgi form validation library.
#!/usr/bin/env newlisp
;; Validation routines library
;;
;; The validation library handles the task of validating fields in a cgi form.
;; After assigning fields (see the examples below), run validation:run with
;; your post-data (from cgi.lsp or elsewhere). The library will validate all
;; fields and assign errors and post values to assoc lists in this context.
;;
;; If validation:run evaluates to nil, you can then use the errors and values
;; to repopulate the form and let the user know they need to fix certain values.
;; If validation:run evaluates as true, you can process the data and continue
;; on however you wish.
;; Examples
;
;(validation:add-field "username" '((validation:required
; "Username is required")
; (validation:min-length 6
; "Username must be at least 5 chars")
; (validation:alpha-numeric
; "Username can only contain letters, numbers, and -/_.")))
;(validation:add-field "password" '((validation:required
; "Password is required")
; (validation:min-length 6
; "Password must be at least 5 chars")
; (validation:alpha-numeric
; "Password can only contain letters, numbers, and -/_.")))
;
;(if (nil? (validation:get-validation-errors post-data))
; ;;Load form again. Errors are available as an assoc list
; ;;(validation:errors) or individually (validation:error "fieldname").
; ;;You can repopulate the form values using (validation:value "fieldname").
; (if validation:errors (println validation:errors) (println "No errors"))
; (println (validation:error "username") (validation:value "username"))
(context 'validation)
;; Handlers
(set 'rules '())
(set 'values '())
(set 'errors '())
(define (add-field fld-name fld-rules)
"Adds field fld-name with fld-rules as a list of
'((validation-function error-string) ...)."
(push (list fld-name fld-rules) rules -1))
(define (vldt value conditions)
"Takes value and tests against parameter list conditions
((function [function-argument] error-string)...). It evaluates the function
with the function-argument as the first parameter and the value as the second.
If there is an error, it throws the error-string. Otherwise, it returns nil."
(dolist (condition conditions)
(if (> 3 (length condition))
(if (nil? (eval (cons (condition 0) value)))
(throw (condition 1)) nil)
(if (nil? (eval (list (condition 0) (condition 1) value)))
(throw (condition 2)) nil))))
(define (run post-data)
(dolist (pair rules)
(let ((err (catch (vldt ((assoc (pair 0) post-data) 1) (pair 1)))))
(if err (begin
(push (list (pair 0) err) errors)
(push (list (pair 0) ((assoc (pair 0) post-data) 1))
values)))))
(if (< 0 (length errors)) nil true))
(define (error fld-name)
(if (assoc fld-name errors) ((assoc fld-name errors) 1) ""))
(define (value fld-name)
(if (assoc fld-name values) ((assoc fld-name values) 1) ""))
;; Validator functions
(define (required value)
"Value exists (not zero length)"
(< 0 (length value)))
(define (max-length len value)
"Value has a maximum length of len"
(<length>= (length value) len))
(define (exact-length len value)
"Value is exactly length len"
(= (length value) len))
(define (alpha? value)
"Value only contains letters, underscores, and whitespace."
(not (regex {[^a-zA-Z_ ]} value 4)))
(define (alpha-dash? value)
"Value only contains letters, underscores, dashes, and whitespace."
(not (regex {[^a-zA-Z_- ]} value 4)))
(define (alpha-numeric? value)
"Value only contains letters, underscores, dashes, numbers and whitespace."
(not (regex {[^a-zA-Z_- 0-9]} value 4)))
(define (valid-phone? value)
"Value is a valid (U.S.) phone number (format: 555-555-5555)."
(regex {^d{3}-d{3}-d{4}$} value))
(define (valid-ip? value)
"Value is a valid IPV4 ip address (0-255 for x.x.x.x)."
(= 4 (length (filter (lambda (val) (and (number? val) (>= val 0) (< val 256)))
(map int (parse value "."))))))
(define (valid-email? value)
"Reasonably usable predicate for email validation.
The regular expression was yanked from Code Igniter."
(regex {^([a-z0-9+_-]+)(.[a-z0-9+_-]+)*@([a-z0-9-]+.)+[a-z]{2,6}$}
value))
(context MAIN)
Don't sell yourself short -- this looks like it has a more general application than just CGI form validations. --Ricky
Very possibly. But because I'm a web programmer, I didn't feel the need to contextually separate out the validators and the validation. I also forgot to note it in the comments that any predicate, referenced with context if not global, can be used in the list of conditions for a field. That way you can use string?, number?, or any user defined ones you want with it. If you look at the valid-ip? predicate, it uses several checks. If any were more generally useful (lower-than-256? is pretty specific) I could have separated them out and made more.
This lib and the template lib I posted are both part of a personal project I'm working on to learn newLISP to create a small web framework for the language (using the cgi and mysql libs from Lutz). I'm having some difficulty deciding how the heck modeling should work, if I want orm-based modeling at all, but I have a feeling that once I get a better feel for context-based prototypes, newLISP should be just about the ultimate orm-based modeling language.
If anyone else would find this useful, I'd love to have some feedback on the sorts of things you would want in a cgi environment. I'm taking a lot of implementation ideas from Django (for Python) and Code Igniter (for PHP), but I don't use many of the libs in those, and I don't want to overwrite the thing.
Any ideas?
I'm not very knowledgeable about this area, but it might be a good idea to check with the newLISP on Noodles folks. It's been very quiet over there recently, but I was under the impression that there was a general move towards writing web framework stuff.
Interesting project. I'll keep an eye on it.