;;;; lazybones.lisp (in-package #:lazybones) (defgeneric handle-request (what request) (:documentation "Implemented for APP and ENDPOINT instances.")) (defclass app () ((name :reader app-name :initarg :name :initform (error "Appname is required") :type symbol) (version :reader app-version :initarg :vsn :initarg :version :initform "0.0.1" :type string) (root :reader app-root :initarg :root :initform "/" :type string) (default-request-authorizer :initarg :default-authorizier :initarg :auth-with :initform nil) (default-http-responders :initarg :default-responders :initform nil :documentation "A PLIST with keys being integers that represent HTTP response codes and with values that are symbols naming responder functions.") (routes :accessor app-routes :initform nil))) (defgeneric dispatch-handler-p (endpoint request) (:documentation "T if ENDPOINT should handle REQUEST, NIL otherwise")) (defclass endpoint () ((method :reader endpoint-method :initarg :method :initform :get) (template :reader endpoint-template :initarg :template :initform (error "endpoint template required")) (dispatch-pattern :reader endpoint-dispatch-pattern) (handler-function :reader endpoint-request-handler) (documentation :reader endpoint-documentation :initarg :doc :initform ""))) (defparameter +http-methods+ (list :get :head :put :post :delete :patch)) (defun parse-route-string-template (template) "Routes are of the form /foo/bar/<>/blah /foo/bar/<>/blah On success returns things like: (\"foo\" \"bar\" (VARIABLE) \"blah\") (\"foo\" \"bar\" (VAR PARSE-INTEGER) \"blah\") Returns NIL on failure" (when (stringp template) (cond ((equal "" template) nil) (t (loop for field in (str:split #\/ template) for var? = (parse-route-variable-string field) when var? collect var? else collect (string-downcase field)))))) (defun parse-route-variable-string (string) "A route variable string looks like <> or <> In the case of a successful parse, a list of one or two symbols is returned. These symbosl are created using read-from-string, which allows for these symbols' packages to be specified if desired. Returns NIL on failre." (when (and (a:starts-with-subseq "<<" string) (a:ends-with-subseq ">>" string)) (destructuring-bind (var-name . decoder?) (re:split " +" (string-trim " " (subseq string 2 (- (length string) 2)))) (if decoder? (list (read-from-string var-name) (read-from-string (first decoder?))) (list (read-from-string var-name)))))) (defun add-route (method routestring handler-function) (assert (member method +http-methods+) nil "~a is not a valid HTTP method indicator." method) )