aboutsummaryrefslogtreecommitdiff
path: root/lazybones.lisp
blob: d72189b5a88ec4165f009f2f878eecf88cf70d0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
;;;; lazybones.lisp

(in-package #:lazybones)

;;; DYNAMIC VARIABLES 

(defgeneric handle-request (what request)
  (:documentation "Implemented for APP and ENDPOINT instances."))

(defgeneric dispatch-handler-p (endpoint request)
  (:documentation "T if ENDPOINT should handle REQUEST, NIL otherwise"))

(defgeneric request-authorized-p (endpoint request)
  (:documentation "Returns T if the REQUEST has authorization to dispatch the handler for ENDPOINT"))


;;; LAZYBONES CLASSES

(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.")
   (endpoints
    :accessor app-endpoints
    :initform nil)))

(defmethod handle-request ((app app) request)
  (a:if-let (endpoint (lookup-endpoint-for app request))
    (handle-request endpoint request)
    (error-response ))

)

(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)
   (app :reader endpoint-app :initarg :app :initform (error "every endpoint must have backlink to an app")
        :documentation "backlink to the app that this endpoint is a part of.")
   (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/<<variable>>/blah 

/foo/bar/<<var parse-integer>>/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 <<foo>> or <<foo bar>>

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 failure."
  (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)
;;   )