cohost

Unofficial Common Lisp client library for Cohost
git clone https://todayiwilllaunchmyinfantsonintoorbit.com/cohost.git
Log | Files | Refs | LICENSE

rpc.lisp (5482B)


      1 (in-package :cohost.rpc)
      2 
      3 (defparameter +user-agent+ (concatenate 'string "CL-Cohost/0.1 " (drakma::user-agent-string :drakma)))
      4 
      5 (defun %cohost-rpc (client rpc-fn method &key request-content params content-type)
      6   "Low-level implementation for RPC methods. Usage identical to COHOST-RPC but can take either PARAMS (prestructured alist) or REQUEST-CONTENT and CONTENT-TYPE (raw POST data) as request body."
      7   (let* ((drakma:*text-content-types* (acons :application :json drakma:*text-content-types*))
      8          (rpc-uri (concatenate 'string (base-uri client) (api-path client) rpc-fn)))
      9     (multiple-value-bind (content http-code headers uri)
     10         (funcall (http-request client) rpc-uri :method method
     11                                                :content request-content
     12                                                :content-type content-type
     13                                                :parameters params
     14                                                :user-agent +user-agent+
     15                                                :cookie-jar (cookie-jar client)
     16                                                :decode-content t)
     17       (values (decode-json-from-string content) http-code headers uri))))
     18 
     19 (defun cohost-rpc (client rpc-fn method &rest params)
     20   "JSON RPC primitive.
     21 
     22 Usage: (COHOST-RPC [initialized client object] [API function to call, eg \"login\"] [HTTP method (:GET or :POST)] (zero or more KEYWORD value pairs expressing parameters, eg :EMAIL \"foo@bar.baz\"))"
     23   (labels ((param-pairs (params)
     24              (loop for (key val) on params by #'cddr
     25                    while val
     26                    collect (cons (sym->str key) (if (numberp val)
     27                                                     (write-to-string val)
     28                                                     val)))))
     29     (let* ((param-alist (param-pairs params)))
     30       (%cohost-rpc client rpc-fn method :params param-alist))))
     31 
     32 (defun cohost-json-rpc (client rpc-fn json &key (method :post))
     33   "JSON RPC primitive for RPC calls that consume a JSON blob directly.
     34 
     35 Usage: (COHOST-JSON-RPC [initialized client object] [API function to call, eg \"post\"] [JSON POST data] (:METHOD :POST or :PUT (default is :POST))"
     36   (%cohost-rpc client rpc-fn method :request-content json :content-type "application/json"))
     37 
     38 (defun cohost-trpc-batch (client requests)
     39   "Consumes a set of tRPC request objects to construct a batch call.
     40 
     41 Usage: (COHOST-TRPC-BATCH [initialized client object] [alist or batch items])
     42 
     43 Each batch item is a CONS structured as ([request] . [input data]). For requests with no data, eg login.loggedIn, the CDR is left NIL."
     44   (multiple-value-bind (batch-items input-items)
     45       (loop for pair in requests
     46             for idx = 0 then (1+ idx)
     47             if (> idx 0)
     48               collect "," into batch-items
     49             collect (car pair) into batch-items
     50             if (cdr pair)
     51               collect (cons idx (cdr pair)) into input-items
     52             finally (return (values batch-items input-items)))
     53     (let* ((batch (apply #'concatenate (append (list 'string "trpc/") batch-items)))
     54            (json::+json-lisp-symbol-tokens+ '(("true" . T)
     55                                               ("null" . nil)
     56                                               ("false" . nil)
     57                                               ("false" . :false)))
     58            (batch-input (encode-json-alist-to-string input-items)))
     59       (cohost-rpc client batch :get :batch 1 :input batch-input))))
     60 
     61 (defmacro define-trpc-request (name (&rest lambda-list))
     62   "Macro for defining tRPC request objects.
     63 
     64 Usage: (DEFINE-TRPC-REQUEST [String name of request, eg \"login.loggedIn\"] ((NIL for requests with no parameters, :atom for requests that take a single atomic value, eg a post ID, or a list of string names of parameters for requests that take JSON input))
     65 
     66 As an example (DEFINE-TRPC-REQUEST \"foo.wibbleFrotz\" ()) would create a function named FOO.WIBBLE-FROTZ that takes no parameters and returns a CONS (\"foo.wibbleFrotz\" . NIL).
     67 
     68 Functions created this way that construct a JSON input can take keyword :FALSE to express JSON false unambiguously. For instance:
     69 (DEFINE-TRPC-REQUEST \"test.bool\" (\"booleanParam\"))
     70 would create a function TEST.BOOL that takes a single parameter. (TEST.BOOL NIL) will result in a null value, {\"booleanParam\":null}, while (TEST.BOOL :FALSE) will emit a Javascript false, {\"booleanParam\":false}. This is clunky but unfortunately necessary as Javascript has different ideas of falsity and nullity than CL."
     71   (labels ((bomb ()
     72              (error "Invalid lambda list for tRPC request (~A)" lambda-list))
     73            (create-ll-and-datum (raw-ll)
     74              (loop for param in raw-ll
     75                    for sym = (gensym)
     76                    collect sym into lambda-list
     77                    collect `(cons ,param ,sym) into a-list
     78                    if (not (stringp param))
     79                      do (bomb)
     80                    finally (return (cons lambda-list `(list ,@a-list))))))
     81     (let* ((lisp-name (intern (json:camel-case-to-lisp name)))
     82            (data-sym (gensym))
     83            (ll-and-datum (cond
     84                            ((eq (car lambda-list) :atom) (cons `(,data-sym) data-sym))
     85                            ((null lambda-list) '())
     86                            ((listp lambda-list) (create-ll-and-datum lambda-list))
     87                            (t (bomb)))))
     88       `(defun ,lisp-name ,(car ll-and-datum)
     89          (cons ,name ,(cdr ll-and-datum))))))