From 46ce31534cf631eeb25963c759979fa4bf19f72b Mon Sep 17 00:00:00 2001 From: Colin Okay Date: Sat, 12 Feb 2022 10:51:27 -0600 Subject: bumped minor version. explicit support for query params in endpoints --- lazybones.asd | 3 ++- lazybones.lisp | 50 ++++++++++++++++++-------------------------------- macros.lisp | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 33 deletions(-) create mode 100644 macros.lisp diff --git a/lazybones.asd b/lazybones.asd index 7ed7adf..415b2c1 100644 --- a/lazybones.asd +++ b/lazybones.asd @@ -4,7 +4,7 @@ :description "http route handling" :author "Colin Okay " :license "AGPLv3" - :version "0.2.0" + :version "0.3.0" :serial t :depends-on (#:alexandria #:str @@ -13,6 +13,7 @@ #:jonathan #:lisp-namespace) :components ((:file "package") + (:file "macros") (:file "lazybones") (:file "lazybones-documentation"))) diff --git a/lazybones.lisp b/lazybones.lisp index c0f1550..03f6c70 100644 --- a/lazybones.lisp +++ b/lazybones.lisp @@ -114,6 +114,13 @@ app, named with the package name. If no app can be found, return NIL" :reader endpoint-route :initarg :route :initform (error "endpoint route required")) + (params + :reader endpoint-params + :initarg :params + :initform nil + :documentation "A list of (SYMBOL-NAME FUNCTION-SYMBOL), + documenting the query parameters this endpoint expects. Used for + generating both documentation and client functions.") (content-type :reader endpoint-content-type :initarg :content-type @@ -309,6 +316,7 @@ any way to do it, hence NIL is returned." (defmacro defendpoint (appname method route + query-params (&key (auth nil) content-type) @@ -322,12 +330,16 @@ making a new one if not." (a:with-gensyms (the-app auth-method) (let* ((dispatch-pattern (parse-route-string-template route)) - (params + (lambda-list (mapcar 'intern (route-variables dispatch-pattern))) (documentation (when (stringp (first body)) (first body))) + (body-without-docstring + (if (stringp (first body)) (rest body) body)) (real-body - (if (stringp (first body)) (rest body) body))) + (if query-params + `((map-parameters ,query-params ,@body-without-docstring)) + body-without-docstring))) `(let* ((,the-app (or (app ',appname) (make-instance 'lazybones:app :name ',appname))) (,auth-method @@ -338,52 +350,26 @@ making a new one if not." 'lazybones:endpoint :method ,method :route ,route + :params ',query-params :content-type ,content-type :pattern ',dispatch-pattern :doc ,documentation :auth ,auth-method :function - (lambda ,params + (lambda ,lambda-list (setf (lazybones:response-header :content-type) (or ,content-type (lazybones::default-content-type ,the-app))) ,@real-body))))))) -(defmacro defendpoint* (method route options &rest body) +(defmacro defendpoint* (method route params options &rest body) "Like DEFENDPOINT but uses the current package name as the app name." - `(defendpoint ,(default-app-name) ,method ,route ,options ,@body)) + `(defendpoint ,(default-app-name) ,method ,route ,params ,options ,@body)) ;;; ENDPOINT HANDLING UTILITIES -(defmacro let-parameters ((&rest names) &body body) - "NAMES is a list of symbols. Binds the names to the value of the -request parameters whose keys compare string-equal to the symbol-name -of each NAME, or NIL if there is no such parameter." - (let ((params (gensym))) - `(let ((,params (lazybones:request-parameters))) - (let ,(loop for name in names - for string-name = (symbol-name name) - collect `(,name (cdr (assoc ,string-name ,params :test #'string-equal)))) - ,@body)))) - -(defmacro map-parameters ((&rest params) &body body) - "PARAMS is a list of pairs (NAME PARSER). MAP-PARAMETERS behaves -exactly like LET-PARAMETERS except that the values boudn to NAMEs are -first mapped with the PARSER function." - (assert (loop for (name parser) in params - always (and (symbolp name) - (or (symbolp parser) (functionp parser)))) - () - "Malformed PARAMS in MAP-PARAMETERS macro") - - (let ((names (mapcar #'car params))) - `(let-parameters ,names - (let ,(loop for name in names - collect `(,name (when ,name (funcall ',(second (assoc name params)) ,name)))) - ,@body)))) - (defun http-ok (content) "Content should be a string, a byte-vector, or a pathname to a local diff --git a/macros.lisp b/macros.lisp new file mode 100644 index 0000000..3832296 --- /dev/null +++ b/macros.lisp @@ -0,0 +1,32 @@ +;;;; macros.lisp --- utility macros + + +(in-package :lazybones) + +(defmacro let-parameters ((&rest names) &body body) + "NAMES is a list of symbols. Binds the names to the value of the +request parameters whose keys compare string-equal to the symbol-name +of each NAME, or NIL if there is no such parameter." + (let ((params (gensym))) + `(let ((,params (lazybones:request-parameters))) + (let ,(loop for name in names + for string-name = (symbol-name name) + collect `(,name (cdr (assoc ,string-name ,params :test #'string-equal)))) + ,@body)))) + +(defmacro map-parameters ((&rest params) &body body) + "PARAMS is a list of pairs (NAME PARSER). MAP-PARAMETERS behaves +exactly like LET-PARAMETERS except that the values boudn to NAMEs are +first mapped with the PARSER function." + (assert (loop for (name parser) in params + always (and (symbolp name) + (or (symbolp parser) (functionp parser)))) + () + "Malformed PARAMS in MAP-PARAMETERS macro") + + (let ((names (mapcar #'car params))) + `(let-parameters ,names + (let ,(loop for name in names + collect `(,name (when ,name (funcall ',(second (assoc name params)) ,name)))) + ,@body)))) + -- cgit v1.2.3