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>
147 lines
5.0 KiB
Plaintext
147 lines
5.0 KiB
Plaintext
;; 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)))
|