diff options
author | Colin Okay <okay@toyful.space> | 2022-03-26 11:39:34 -0500 |
---|---|---|
committer | Colin Okay <okay@toyful.space> | 2022-03-26 11:39:34 -0500 |
commit | d81bcd3d57b51f95a27f40bb2ba9264d5d805b69 (patch) | |
tree | 1150bcef4517eeac26b7077736eb3b61afa05c08 | |
parent | 95ba9f0b4b4ca9e53187791db8a3e58648fb038a (diff) |
[redesign] to be more consistent and less awkward
Though sometimes more verbose.
This redesign has dropped the request-with macro and simply generates
a package exporting only function names. those functions make requests
to the app.
-rw-r--r-- | lazybones-client.lisp | 230 |
1 files changed, 106 insertions, 124 deletions
diff --git a/lazybones-client.lisp b/lazybones-client.lisp index d5dac4f..87f5e90 100644 --- a/lazybones-client.lisp +++ b/lazybones-client.lisp @@ -18,134 +18,116 @@ (defpackage #:lazybones-client (:use #:cl) + (:local-nicknames (#:a #:alexandria-2)) (:export #:generate-client-system)) -;; dummy dexador package to keep lisp from complainging during code -;; generation +(in-package :lazybones-client) -(defpackage #:dexador - (:export #:get #:put #:post #:head #:patch #:delete)) -(defpackage #:lazybones-client.shared - (:intern #:req-string) - (:export #:*host* #:*body* #:*cookies* #:*headers*)) -(in-package :lazybones-client) +(defun endpoint-defun-name (ep) + "Returns the string name of a defun for making requests to +endpoint EP." + (with-output-to-string (*standard-output*) + (princ (string-downcase (symbol-name (lazybones::endpoint-method ep)))) + (princ "/") + (loop for (term . more) on (lazybones::endpoint-dispatch-pattern ep) + when (and (stringp term) (plusp (length term))) + do (princ term) + when (listp term) + do (princ (car term)) + when more + do (princ "-")))) + +(defun endpoint-defun-route-var-names (ep) + "Returns a list of strings representing the names of route variables +extracted from endpoint EP, to be used as variable names in the defun +for making requests to that endpoint." + (mapcar 'symbol-name + (lazybones::endpoint-route-vars ep))) + +(defun endpoint-defun-query-var-names (ep) + "Returns a list of strings representing the names of query parameter +variables extraced from the endpoint EP, to be used as variable names +in the defun for making request to that endpoint." + (mapcar (a:compose #'symbol-name #'first) + (lazybones::endpoint-params ep))) + +(defun endpoint-defun-lambda-list (ep) + "Returns a string representation of the lambda list of the defun +for making requests to endpoint EP." + (format + nil + "(%host% %headers% %cookies% ~:[~;%content-type% %body% ~]&key ~{~a ~})" + (member (lazybones::endpoint-method ep) '(:post :put :patch)) + (append + (endpoint-defun-route-var-names ep) + (endpoint-defun-query-var-names ep)))) + + +(defun endpoint-defun-dexador-uri-route-format-string (ep) + "Returns a string representing a format string, intended to be + embedded into the body of a defun for making requests to the + endpoint Ep. It is designed to be passed to FORMAT, where route + variables are substituted into the string." + (str:join "/" + (mapcar (lambda (x) (if (listp x) "~a" x)) + (lazybones::endpoint-dispatch-pattern ep)))) + +(defun endpoint-defun-dexador-uri-route-query-format-string (ep) + "Returns a string representing a format string, intended to be + embedded into the body of a defun for making requests to the + endpoint EP. It is desienged to be passed to FORMAT, where query + paramters are substituted into the string, if they exist." + (str:join "&" + (loop for varname in (endpoint-defun-query-var-names ep) + collect (with-output-to-string (*standard-output*) + (princ "~:[~;") + (princ (string-upcase varname)) + (princ "=~a~]"))))) + +(defun endpoint-defun-dexador-request-uri (app ep) + "Returns a string representation of code that generates a URI for + passing to the dexador request function within the body of the defun + for making requests to the endpoint EP of the application APP." + (concatenate + 'string + "(format nil " + "\"" + "~a" + (lazybones::app-prefix app) + (endpoint-defun-dexador-uri-route-format-string ep) + "?" + (endpoint-defun-dexador-uri-route-query-format-string ep) + "\" " + (str:join " " (endpoint-defun-route-var-names ep)) + (str:join " " (endpoint-defun-query-var-names ep)))) + +(defun endpoint-defun-body (app ep) + "Returns a string representation of the function body of a defun + for making requests to the endpoint EP in the app APP." + (format + nil + "(dexador:~a~%~s~%~{~a~%~})" + (string-downcase (symbol-name (lazybones::endpoint-method ep))) + (endpoint-defun-dexador-request-uri app ep) + '(":content %body%" + ":cookie-jar %cookies%" + ":headers (append (when %content-type% (list (cons \"Content-Type\" %content-type%))) %headers%)"))) + +(defun generate-defun-for-endpoint (app ep) + "Returns a string representation of a defun form for a function that +makes a request to the endpoint EP." + (format nil + "(defun ~a ~a~%~s~%~a)" + (endpoint-defun-name ep) + (endpoint-defun-lambda-list ep) + (lazybones::endpoint-documentation ep) + (endpoint-defun-body app ep))) -(defparameter +preamble+ - "(defvar *host* nil - \"The host to which the client will send its requests.\") - - (defvar *body* nil - \"Body passed to client post, put, and patch requests\") - - (defvar *cookies* nil - \"An instance of CL-COOKIE:COOKIE-JAR.\") - - (defvar *headers* nil - \"A liist of (header-name . header-value) pairs.\") - - (defmacro request-with ((&key host body headers content-type cookies) &body forms) - \"Make a request in a specific context. - -HOST is a string, the hostname where the request will be sent. Defaults -to *HOST*. - -BODY should be a string, an alist, or a pathname. Default to *BODY* - -HEADERS should be an ALIST of (header-name . header-value) string -pairs. Defaults to *HEADERS*. - -CONTENT-TYPE is a convenience for supplying just the Content-Type -header. - -COOKIES should be an instance of CL-COOKIE:COOKIE-JAR. Defaults to -*COOKIES*. -\" - (let ((content-type-var (gensym)) - (http-error-var (gensym))) - `(let ((*host* (or ,host *host*)) - (*body* (or ,body *body*)) - (*headers* (or ,headers *headers*)) - (*cookies* (or ,cookies *cookies*)) - (,content-type-var ,content-type)) - (when ,content-type-var - (push (cons \"Content-Type\" ,content-type-var) *headers*)) - (handler-case (progn ,@forms) - (dex:http-request-failed (,http-error-var) - (format *error-output* \"~a -- ~a\" - (dex:response-status ,http-error-var) - (dex:response-body ,http-error-var)))))))") - -(defun make-defun-name (method dispatch-pattern) - "Utility for making function names for endpoint request functions." - (intern - (with-output-to-string (*standard-output*) - (princ method) (princ "-") - (loop for (term . more) on dispatch-pattern - when (and (stringp term) (plusp (length term))) - do (princ (string-upcase term)) - when (listp term) - do (princ (string-upcase (car term))) - when more - do (princ "-"))))) - -(defun endpoint-to-defun-name (ep) - (with-slots (lazybones::method lazybones::dispatch-pattern) ep - (make-defun-name lazybones::method lazybones::dispatch-pattern))) - -(defun dispatch-pattern-to-format-string (pattern) - (str:join "/" (mapcar (lambda (x) (if (listp x) "~a" x)) pattern))) - -(defun param-to-string (p &optional prefix) - (if prefix - `(if ,p (concatenate 'string "&" (symbol-name ',p) "=" (format nil "~a" ,p)) "") - `(if ,p (concatenate 'string (symbol-name ',p) "=" (format nil "~a" ,p)) ""))) - -(defun params-to-query-string (params) - (loop for count from 0 - for p in params - collect (param-to-string p (plusp count)))) - -(defun generate-client-function-for-endpoint (app-prefix ep) - (let ((defun-name - (endpoint-to-defun-name ep)) - (vars - (mapcar 'intern (lazybones::endpoint-route-vars ep))) - (format-string - (dispatch-pattern-to-format-string (lazybones::endpoint-dispatch-pattern ep))) - (qparams - (mapcar - (lambda (x) (intern (symbol-name (first x)))) - (lazybones::endpoint-params ep))) - (content-p - (member (lazybones::endpoint-method ep) - '(:put :post :patch))) - (dex-fn - (intern (symbol-name (lazybones::endpoint-method ep)) :dexador))) - `(defun ,defun-name (,@vars ,@(when qparams (cons '&key qparams))) - ,(lazybones::endpoint-documentation ep) - (let ((lazybones-client.shared::req-string - (apply #'concatenate - 'string - lazybones-client.shared:*host* - ,app-prefix - (format nil ,format-string ,@vars) - ,(when qparams `(when (or ,@qparams) - (list "?" ,@(params-to-query-string qparams))))))) - ,(if content-p - `(if lazybones-client.shared:*body* - (,dex-fn lazybones-client.shared::req-string :content lazybones-client.shared:*body* :cookie-jar lazybones-client.shared:*cookies* :headers lazybones-client.shared:*headers*) - (,dex-fn lazybones-client.shared::req-string :cookie-jar lazybones-client.shared:*cookies* :headers lazybones-client.shared:*headers*)) - `(,dex-fn lazybones-client.shared::req-string :cookie-jar lazybones-client.shared:*cookies* :headers lazybones-client.shared:*headers*)))))) - - -(defun generate-client-functions-for-app (app) - (loop for ep in (lazybones::app-endpoints app) - collect (generate-client-function-for-endpoint (lazybones::app-prefix app) ep))) (defun all-function-names (app) - (mapcar 'endpoint-to-defun-name (lazybones::app-endpoints app))) + (mapcar 'endpoint-defun-name (lazybones::app-endpoints app))) (defun app-client-package-name (app) (format nil "~a-CLIENT" (lazybones::app-name app))) @@ -163,17 +145,15 @@ COOKIES should be an instance of CL-COOKIE:COOKIE-JAR. Defaults to (princ #\") (princ ")))"))) + (defun generate-defpackage-for-client-of-app (app) (with-output-to-string (out) (format out " -(defpackage #:lazybones-client.shared - (:intern #:req-string) - (:export #:*host* #:*body* #:*headers* #:*cookies*)) (defpackage #:~a (:use :cl :lazybones-client.shared) - (:export #:*host* #:*body* #:*headers* #:*cookies* #:request-with~% ~{#:~a~^~%~}))" + (:export ~%~{ #:~a~%~}))" (app-client-package-name app) (all-function-names app)) (terpri out) @@ -188,9 +168,11 @@ COOKIES should be an instance of CL-COOKIE:COOKIE-JAR. Defaults to (defun generate-client-system (directory app) (assert (uiop:directory-exists-p directory)) + (alexandria:write-string-into-file (generate-defsystem-for-client-of-app app) (merge-pathnames (client-asd-file-name app) directory)) + (alexandria:write-string-into-file (with-output-to-string (*standard-output*) (princ (generate-defpackage-for-client-of-app app)) |