Files
rose-ash/sx/sx/handlers/ref-api.sx
giles feecbb66ba Convert all API endpoint URLs to SX expression format
Every URL at sx-web.org now uses bracketed SX expressions — pages AND
API endpoints. defhandler :path values, sx-get/sx-post/sx-delete attrs,
code examples, and Python route decorators all converted.

- Add SxAtomConverter to handlers.py for parameter matching inside
  expression URLs (e.g. /(api.(item.<sx:item_id>)))
- Convert ~50 defhandler :path values in ref-api.sx and examples.sx
- Convert ~90 sx-get/sx-post/sx-delete URLs in reference.sx, examples.sx
- Convert ~30 code example URLs in examples-content.sx
- Convert ~30 API URLs in pages.py (Python string code examples)
- Convert ~70 page navigation URLs in pages.py
- Convert 7 Python route decorators in routes.py
- Convert ~10 reactive API URLs in marshes.sx
- Add API redirect patterns to sx_router.py (301 for old paths)
- Remove /api/ skip in app.py redirects (old API paths now redirect)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:02:26 +00:00

298 lines
11 KiB
Plaintext

;; Reference API endpoints — live demos for hypermedia attribute docs
;;
;; These replace the Python endpoints in bp/pages/routes.py.
;; Each defhandler with :path registers as a public route automatically.
;; --- sx-get demo: server time ---
(defhandler ref-time
:path "/(geography.(hypermedia.(reference.(api.time))))"
:method :get
:returns "element"
(&key)
(let ((now (now "%H:%M:%S")))
(<>
(span :class "text-stone-800 text-sm" "Server time: " (strong now))
(~doc-oob-code :target-id "ref-wire-sx-get"
:text (str "(span :class \"text-stone-800 text-sm\" \"Server time: \" (strong \"" now "\"))")))))
;; --- sx-post demo: greet ---
(defhandler ref-greet
:path "/(geography.(hypermedia.(reference.(api.greet))))"
:method :post
:csrf false
:returns "element"
(&key)
(let ((name (request-form "name" "stranger")))
(<>
(span :class "text-stone-800 text-sm" "Hello, " (strong name) "!")
(~doc-oob-code :target-id "ref-wire-sx-post"
:text (str "(span :class \"text-stone-800 text-sm\" \"Hello, \" (strong \"" name "\") \"!\")")))))
;; --- sx-put demo: status update ---
(defhandler ref-status
:path "/(geography.(hypermedia.(reference.(api.status))))"
:method :put
:csrf false
:returns "element"
(&key)
(let ((status (request-form "status" "unknown")))
(<>
(span :class "text-stone-700 text-sm" "Status: " (strong status) " — updated via PUT")
(~doc-oob-code :target-id "ref-wire-sx-put"
:text (str "(span :class \"text-stone-700 text-sm\" \"Status: \" (strong \"" status "\") \" — updated via PUT\")")))))
;; --- sx-patch demo: theme ---
(defhandler ref-theme
:path "/(geography.(hypermedia.(reference.(api.theme))))"
:method :patch
:csrf false
:returns "element"
(&key)
(let ((theme (request-form "theme" "unknown")))
(<>
theme
(~doc-oob-code :target-id "ref-wire-sx-patch"
:text (str "\"" theme "\"")))))
;; --- sx-delete demo ---
(defhandler ref-delete-item
:path "/(geography.(hypermedia.(reference.(api.(item.<sx:item_id>)))))"
:method :delete
:csrf false
:returns "element"
(&key)
(<>
(~doc-oob-code :target-id "ref-wire-sx-delete" :text "\"\"")))
;; --- sx-trigger demo: search ---
(defhandler ref-trigger-search
:path "/(geography.(hypermedia.(reference.(api.trigger-search))))"
:method :get
:returns "element"
(&key)
(let ((q (request-arg "q" "")))
(let ((sx-text (if (= q "")
"(span :class \"text-stone-400 text-sm\" \"Start typing to trigger a search.\")"
(str "(span :class \"text-stone-800 text-sm\" \"Results for: \" (strong \"" q "\"))"))))
(<>
(if (= q "")
(span :class "text-stone-400 text-sm" "Start typing to trigger a search.")
(span :class "text-stone-800 text-sm" "Results for: " (strong q)))
(~doc-oob-code :target-id "ref-wire-sx-trigger" :text sx-text)))))
;; --- sx-swap demo ---
(defhandler ref-swap-item
:path "/(geography.(hypermedia.(reference.(api.swap-item))))"
:method :get
:returns "element"
(&key)
(let ((now (now "%H:%M:%S")))
(<>
(div :class "text-sm text-violet-700" (str "New item (" now ")"))
(~doc-oob-code :target-id "ref-wire-sx-swap"
:text (str "(div :class \"text-sm text-violet-700\" \"New item (" now ")\")")))))
;; --- sx-swap-oob demo ---
(defhandler ref-oob
:path "/(geography.(hypermedia.(reference.(api.oob))))"
:method :get
:returns "element"
(&key)
(let ((now (now "%H:%M:%S")))
(<>
(span :class "text-emerald-700 text-sm" "Main updated at " now)
(div :id "ref-oob-side" :sx-swap-oob "innerHTML"
(span :class "text-violet-700 text-sm" "OOB updated at " now))
(~doc-oob-code :target-id "ref-wire-sx-swap-oob"
:text (str "(<> (span ... \"" now "\") (div :id \"ref-oob-side\" :sx-swap-oob \"innerHTML\" ...))")))))
;; --- sx-select demo ---
(defhandler ref-select-page
:path "/(geography.(hypermedia.(reference.(api.select-page))))"
:method :get
:returns "element"
(&key)
(let ((now (now "%H:%M:%S")))
(<>
(div :id "the-header" (h3 "Page header — not selected"))
(div :id "the-content"
(span :class "text-emerald-700 text-sm"
"This fragment was selected from a larger response. Time: " now))
(div :id "the-footer" (p "Page footer — not selected"))
(~doc-oob-code :target-id "ref-wire-sx-select"
:text (str "(<> (div :id \"the-header\" ...) (div :id \"the-content\" ... \"" now "\") (div :id \"the-footer\" ...))")))))
;; --- sx-sync demo: slow echo ---
(defhandler ref-slow-echo
:path "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:method :get
:returns "element"
(&key)
(let ((q (request-arg "q" "")))
(sleep 800)
(<>
(span :class "text-stone-800 text-sm" "Echo: " (strong q))
(~doc-oob-code :target-id "ref-wire-sx-sync"
:text (str "(span :class \"text-stone-800 text-sm\" \"Echo: \" (strong \"" q "\"))")))))
;; --- sx-prompt demo ---
(defhandler ref-prompt-echo
:path "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:method :get
:returns "element"
(&key)
(let ((name (request-header "SX-Prompt" "anonymous")))
(<>
(span :class "text-stone-800 text-sm" "Hello, " (strong name) "!")
(~doc-oob-code :target-id "ref-wire-sx-prompt"
:text (str "(span :class \"text-stone-800 text-sm\" \"Hello, \" (strong \"" name "\") \"!\")")))))
;; --- Error demo ---
(defhandler ref-error-500
:path "/(geography.(hypermedia.(reference.(api.error-500))))"
:method :get
:returns "nil"
(&key)
(abort 500 "Server error"))
;; ==========================================================================
;; Remaining reference endpoints — migrated from Python
;; ==========================================================================
;; --- sx-encoding demo: file upload name ---
(defhandler ref-upload-name
:path "/(geography.(hypermedia.(reference.(api.upload-name))))"
:method :post
:csrf false
:returns "element"
(&key)
(let ((name (request-file-name "file")))
(let ((display (if (nil? name) "(no file)" name)))
(let ((sx-text (str "(span :class \"text-stone-800 text-sm\" \"Received: \" (strong \"" display "\"))")))
(<>
(span :class "text-stone-800 text-sm" "Received: " (strong display))
(~doc-oob-code :target-id "ref-wire-sx-encoding" :text sx-text))))))
;; --- sx-headers demo: echo custom headers ---
(defhandler ref-echo-headers
:path "/(geography.(hypermedia.(reference.(api.echo-headers))))"
:method :get
:returns "element"
(&key)
(let ((all-headers (into (list) (request-headers-all))))
(let ((custom (filter
(fn (pair) (starts-with? (first pair) "x-"))
all-headers)))
(let ((sx-text
(if (empty? custom)
"(span :class \"text-stone-400 text-sm\" \"No custom headers received.\")"
(str "(ul :class \"text-sm text-stone-700 space-y-1\" "
(join " " (map (fn (pair) (str "(li (strong \"" (first pair) "\") \": \" \"" (nth pair 1) "\")")) custom))
")"))))
(<>
(if (empty? custom)
(span :class "text-stone-400 text-sm" "No custom headers received.")
(ul :class "text-sm text-stone-700 space-y-1"
(map (fn (pair) (li (strong (first pair)) ": " (nth pair 1))) custom)))
(~doc-oob-code :target-id "ref-wire-sx-headers" :text sx-text))))))
;; --- sx-include demo: echo GET query params ---
(defhandler ref-echo-vals-get
:path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:method :get
:returns "element"
(&key)
(let ((vals (into (list) (request-args-all))))
(let ((sx-text
(if (empty? vals)
"(span :class \"text-stone-400 text-sm\" \"No values received.\")"
(str "(ul :class \"text-sm text-stone-700 space-y-1\" "
(join " " (map (fn (pair) (str "(li (strong \"" (first pair) "\") \": \" \"" (nth pair 1) "\")")) vals))
")"))))
(<>
(if (empty? vals)
(span :class "text-stone-400 text-sm" "No values received.")
(ul :class "text-sm text-stone-700 space-y-1"
(map (fn (pair) (li (strong (first pair)) ": " (nth pair 1))) vals)))
(~doc-oob-code :target-id "ref-wire-sx-include" :text sx-text)))))
;; --- sx-vals demo: echo POST form values ---
(defhandler ref-echo-vals-post
:path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:method :post
:csrf false
:returns "element"
(&key)
(let ((vals (into (list) (request-form-all))))
(let ((sx-text
(if (empty? vals)
"(span :class \"text-stone-400 text-sm\" \"No values received.\")"
(str "(ul :class \"text-sm text-stone-700 space-y-1\" "
(join " " (map (fn (pair) (str "(li (strong \"" (first pair) "\") \": \" \"" (nth pair 1) "\")")) vals))
")"))))
(<>
(if (empty? vals)
(span :class "text-stone-400 text-sm" "No values received.")
(ul :class "text-sm text-stone-700 space-y-1"
(map (fn (pair) (li (strong (first pair)) ": " (nth pair 1))) vals)))
(~doc-oob-code :target-id "ref-wire-sx-vals" :text sx-text)))))
;; --- sx-retry demo: flaky endpoint (fails 2/3 times) ---
(defhandler ref-flaky
:path "/(geography.(hypermedia.(reference.(api.flaky))))"
:method :get
:returns "element"
(&key)
(let ((n (+ (state-get "ref-flaky-n" 0) 1)))
(state-set! "ref-flaky-n" n)
(if (not (= (mod n 3) 0))
(do
(set-response-status 503)
"")
(let ((sx-text (str "(span :class \"text-emerald-700 text-sm\" \"Success on attempt \" \"" n "\" \"!\")")))
(<>
(span :class "text-emerald-700 text-sm" "Success on attempt " (str n) "!")
(~doc-oob-code :target-id "ref-wire-sx-retry" :text sx-text))))))
;; --- sx-trigger-event demo: response header triggers ---
(defhandler ref-trigger-event
:path "/(geography.(hypermedia.(reference.(api.trigger-event))))"
:method :get
:returns "element"
(&key)
(let ((now (now "%H:%M:%S")))
(set-response-header "SX-Trigger" "showNotice")
(<>
(span :class "text-stone-800 text-sm" "Loaded at " (strong now) " — check the border!"))))
;; --- sx-retarget demo: response header retargets ---
(defhandler ref-retarget
:path "/(geography.(hypermedia.(reference.(api.retarget))))"
:method :get
:returns "element"
(&key)
(let ((now (now "%H:%M:%S")))
(set-response-header "SX-Retarget" "#ref-hdr-retarget-alt")
(<>
(span :class "text-violet-700 text-sm" "Retargeted at " (strong now)))))