Hello all,
I propose that the types for easy-handlers be extensible by the users. I have pieces of markup which are used throughout our site which collect information from several fields into a class (in our case tied to a database with clsql). The names of the fields are related, but diffierent from the lists, arrays and hashes of the easy-handlers, Dates and addresses are two good examples, but there are others. I would like to be able to teach the easy handler to collect these for me and pass them as parameters. This cannot be done with functions as the :parameter-type because the functions passed as :parameter-type do not take the parameter name.
There are two ways I think this could be done:
- The easier way: add a :paramater-reader arg to the parameter description, and if passed use it in lieu of the ((post|get)-)?parameter functions in compute-parameter. The following might do it (untested):
(defun compute-parameter (parameter-name parameter-type request-type
&optional parameter-reader) ; <=====
"Computes and returns the parameter\(s) called PARAMETER-NAME
and converts it/them according to the value of PARAMETER-TYPE.
REQUEST-TYPE is one of :GET, :POST, or :BOTH."
(when (member parameter-type '(list array hash-table))
(setq parameter-type (list parameter-type 'string)))
(let ((parameter-reader (or parameter-reader ; <====
(ecase request-type
(:get #'get-parameter)
(:post #'post-parameter)
(:both #'parameter))) )
(parameters (and (listp parameter-type)
(case request-type
(:get (get-parameters))
(:post (post-parameters))
(:both (append (get-parameters) (post-parameters)))))))
(cond ((atom parameter-type)
(compute-simple-parameter parameter-name parameter-type parameter-reader))
((and (null (cddr parameter-type))
(eq (first parameter-type) 'list))
(compute-list-parameter parameter-name (second parameter-type) parameters))
((and (null (cddr parameter-type))
(eq (first parameter-type) 'array))
(compute-array-parameter parameter-name (second parameter-type) parameters))
((and (null (cddddr parameter-type))
(eq (first parameter-type) 'hash-table))
(compute-hash-table-parameter parameter-name (second parameter-type) parameters
(or (third parameter-type) 'string)
(or (fourth parameter-type) 'equal)))
(t (error "Don't know what to do with parameter type ~S." parameter-type)))))
(defun make-defun-parameter (description default-parameter-type default-request-type)
"Creates a keyword parameter to be used by DEFINE-EASY-HANDLER.
DESCRIPTION is one of the elements of DEFINE-EASY-HANDLER's
LAMBDA-LIST and DEFAULT-PARAMETER-TYPE and DEFAULT-REQUEST-TYPE
are the global default values."
(when (atom description)
(setq description (list description)))
(destructuring-bind (parameter-name &key (real-name (compute-real-name parameter-name))
parameter-type init-form request-type parameter-reader ) ; <====
description
`(,parameter-name (or (and (boundp '*request*)
(compute-parameter ,real-name
,(or parameter-type default-parameter-type)
,(or request-type default-request-type)))
,init-form))))
(This just occurred to me: request-type could be a function, with an obvious change to the ecase. This is a little more confusing but consistent with what parameter-type does---so it has the force of precedent---and requires a smaller change to the code.).
- The extensive way: Change the cond in compute-parameter to a switch via a hash table, whose values are the conversion handlers (or a cons with reader and converter). It may need two hash tables: one for simple type names (type is a symbol) and one for aggregates, (type is a cons, and switch on the first element). Create a macro, define-easy-type, or something, to populate the tables, and use it within easy-handlers.lisp to populate it with those which are now hardcoded in the cond and case switches. Users can then use the macro to define their own types.
- Another way: like above, but use methods instead of hash tables. This seems possible, but I'd have to think some more about it.
BTW: why the checks for null CDDRs and CDDDDRs in compute-parameter? Is this just nice, or are they essential?
If your interested in the second, I will work something up and send in a patch. I do not think it would break any existing code which did not peak too far into the easy-handlers stuff.
Let me know what you think.
Tim S
P.S.:
Hunchentoot is a pleasure to work with!