dream: core types — request/response/route records + 41 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m3s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m3s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
146
lib/dream/types.sx
Normal file
146
lib/dream/types.sx
Normal file
@@ -0,0 +1,146 @@
|
||||
;; lib/dream/types.sx — Dream-on-SX core types.
|
||||
;; The five types: request, response, route. handler = request->response and
|
||||
;; middleware = handler->handler are plain SX functions (no records needed).
|
||||
;; request/response/route are dicts. Headers are dicts with lowercased string
|
||||
;; keys; keywords are strings in SX, so :content-type == "content-type".
|
||||
|
||||
;; ── internal helpers ───────────────────────────────────────────────
|
||||
(define
|
||||
dr/normalize-headers
|
||||
(fn
|
||||
(h)
|
||||
(reduce
|
||||
(fn (acc k) (assoc acc (lower k) (get h k)))
|
||||
{}
|
||||
(keys h))))
|
||||
|
||||
(define
|
||||
dr/path-of
|
||||
(fn
|
||||
(target)
|
||||
(let
|
||||
((i (index-of target "?")))
|
||||
(if (< i 0) target (substr target 0 i)))))
|
||||
|
||||
(define
|
||||
dr/query-of
|
||||
(fn
|
||||
(target)
|
||||
(let
|
||||
((i (index-of target "?")))
|
||||
(if (< i 0) "" (substr target (+ i 1))))))
|
||||
|
||||
(define
|
||||
dr/parse-pair
|
||||
(fn
|
||||
(acc pair)
|
||||
(if
|
||||
(= pair "")
|
||||
acc
|
||||
(let
|
||||
((j (index-of pair "=")))
|
||||
(if
|
||||
(< j 0)
|
||||
(assoc acc pair "")
|
||||
(assoc
|
||||
acc
|
||||
(substr pair 0 j)
|
||||
(substr pair (+ j 1))))))))
|
||||
|
||||
(define
|
||||
dr/parse-query
|
||||
(fn
|
||||
(target)
|
||||
(let
|
||||
((q (dr/query-of target)))
|
||||
(if
|
||||
(= q "")
|
||||
{}
|
||||
(reduce dr/parse-pair {} (split q "&"))))))
|
||||
|
||||
;; ── request ────────────────────────────────────────────────────────
|
||||
(define dream-request (fn (method target headers body) {:path (dr/path-of target) :params {} :query (dr/parse-query target) :body body :headers (dr/normalize-headers headers) :method (upper method) :target target}))
|
||||
|
||||
(define
|
||||
dream-request?
|
||||
(fn (x) (and (dict? x) (has-key? x :method) (has-key? x :path))))
|
||||
(define dream-method (fn (req) (get req :method)))
|
||||
(define dream-target (fn (req) (get req :target)))
|
||||
(define dream-path (fn (req) (get req :path)))
|
||||
(define dream-body (fn (req) (get req :body)))
|
||||
(define
|
||||
dream-header
|
||||
(fn (req name) (get (get req :headers) (lower name))))
|
||||
(define dream-query-param (fn (req name) (get (get req :query) name)))
|
||||
(define dream-param (fn (req name) (get (get req :params) name)))
|
||||
(define dream-params (fn (req) (get req :params)))
|
||||
|
||||
;; router fills path params during dispatch
|
||||
(define
|
||||
dream-with-param
|
||||
(fn
|
||||
(req name val)
|
||||
(assoc req :params (assoc (get req :params) name val))))
|
||||
(define
|
||||
dream-with-params
|
||||
(fn
|
||||
(req more)
|
||||
(assoc
|
||||
req
|
||||
:params (reduce
|
||||
(fn (acc k) (assoc acc k (get more k)))
|
||||
(get req :params)
|
||||
(keys more)))))
|
||||
(define dream-set-body (fn (req body) (assoc req :body body)))
|
||||
|
||||
;; ── response ───────────────────────────────────────────────────────
|
||||
(define dream-response (fn (status headers body) {:body body :headers (dr/normalize-headers headers) :status status}))
|
||||
|
||||
(define
|
||||
dream-response?
|
||||
(fn (x) (and (dict? x) (has-key? x :status) (has-key? x :body))))
|
||||
(define dream-status (fn (resp) (get resp :status)))
|
||||
(define
|
||||
dream-resp-header
|
||||
(fn (resp name) (get (get resp :headers) (lower name))))
|
||||
(define dream-resp-body (fn (resp) (get resp :body)))
|
||||
(define dream-headers (fn (resp) (get resp :headers)))
|
||||
|
||||
(define
|
||||
dream-add-header
|
||||
(fn
|
||||
(resp name val)
|
||||
(assoc resp :headers (assoc (get resp :headers) (lower name) val))))
|
||||
(define dream-set-status (fn (resp status) (assoc resp :status status)))
|
||||
|
||||
;; smart constructors
|
||||
(define dream-html (fn (body) (dream-response 200 {:content-type "text/html; charset=utf-8"} body)))
|
||||
(define
|
||||
dream-html-status
|
||||
(fn (status body) (dream-response status {:content-type "text/html; charset=utf-8"} body)))
|
||||
(define dream-text (fn (body) (dream-response 200 {:content-type "text/plain; charset=utf-8"} body)))
|
||||
(define dream-json (fn (body) (dream-response 200 {:content-type "application/json"} body)))
|
||||
(define dream-empty (fn (status) (dream-response status {} "")))
|
||||
(define
|
||||
dream-not-found
|
||||
(fn () (dream-response 404 {:content-type "text/plain; charset=utf-8"} "Not Found")))
|
||||
(define
|
||||
dream-redirect
|
||||
(fn (location) (dream-response 303 {:location location} "")))
|
||||
(define
|
||||
dream-redirect-status
|
||||
(fn (status location) (dream-response status {:location location} "")))
|
||||
|
||||
;; coerce a handler result: strings become 200 text/html responses
|
||||
(define
|
||||
dream-coerce-response
|
||||
(fn (x) (if (dream-response? x) x (dream-html x))))
|
||||
|
||||
;; ── route ──────────────────────────────────────────────────────────
|
||||
(define dream-route (fn (method path handler) {:path path :handler handler :method (upper method)}))
|
||||
(define
|
||||
dream-route?
|
||||
(fn (x) (and (dict? x) (has-key? x :handler) (has-key? x :path))))
|
||||
(define dream-route-method (fn (r) (get r :method)))
|
||||
(define dream-route-path (fn (r) (get r :path)))
|
||||
(define dream-route-handler (fn (r) (get r :handler)))
|
||||
Reference in New Issue
Block a user