blob: ba70a484d219788d878b52f12286dc1e8ef19dc1 (
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
|
(define-module (server macro)
:export (make-routes)
:use-module (util)
:use-module (ice-9 regex)
:use-module (srfi srfi-1)
:use-module (web response)
:use-module (web uri))
(define-public (parse-endpoint-string str)
(let ((rx (make-regexp ":([^/.]+)(\\{([^}]+)\\})?([.])?")))
(let loop ((str str)
(string "")
(tokens '()))
(let ((m (regexp-exec rx str 0)))
(if (not m)
;; done
(values (string-append string str) (reverse tokens))
(loop (match:suffix m)
(string-append string (match:prefix m)
(aif (match:substring m 3)
(string-append "(" it ")")
"([^/.]+)")
;; period directly following matched variable.
;; since many variables break on period, we often
;; want to match a literal period directly after them.
;; Ideally all periods outside of pattern should be
;; matched literally, but that's harder to implement.
(regexp-quote
(aif (match:substring m 4)
"." "")))
(cons (string->symbol (match:substring m 1))
tokens)))))))
(define (generate-case defn)
(let* (((method uri param-list . body) defn)
(regex tokens (parse-endpoint-string uri))
(diff intersect (lset-diff+intersection eq? param-list tokens)))
`((and (eq? r:method (quote ,method))
(regexp-exec (make-regexp ,(string-append "^" regex "/?$") regexp/icase)
r:path))
=> (lambda (match-object)
;; (assert
;; (= (1- (match:count match-object))
;; (length intersect)))
;; Those parameters which were present in the template uri
((lambda ,intersect
;; Those that only are in the query string
(lambda* (,@(unless (null? diff) `(#:key ,@diff #:allow-other-keys))
#:rest rest)
,@body))
,@(unless (null? intersect)
(map (lambda (i)
`(match:substring match-object ,i))
(cdr (iota (1+ (length intersect)))))))))))
(define-macro (make-routes . routes)
`(lambda* (request body #:optional state)
;; ALl these bindings generate compile time warnings since the expansion
;; of the macro might not use them. This isn't really a problem.
(let ((r:method (request-method request))
(r:uri (request-uri request))
(r:version (request-version request))
(r:headers (request-headers request))
(r:meta (request-meta request))
(r:port (request-port request)))
(let ((r:scheme (uri-scheme r:uri))
(r:userinfo (uri-userinfo r:uri))
(r:host (uri-host r:uri))
(r:port (uri-port r:uri))
(r:path (uri-path r:uri))
(r:query (uri-query r:uri))
(r:fragment (uri-fragment r:uri)))
(call-with-values
(lambda ()
(call/ec (lambda (return)
(apply
(cond ,@(map generate-case routes)
(else (lambda* _ (return (build-response #:code 404)
"404 Not Fonud"))))
(append
(parse-query r:query)
(when (memv 'application/x-www-form-urlencoded
(or (assoc-ref r:headers 'content-type) '()))
(parse-query (uri-decode (bytevector->string body "UTF-8")))))))))
(lambda* (a b #:optional new-state)
(values a b (or new-state state))))))))
|