Playwright API testing — APIRequest, APIRequestContext, APIResponse.
Provides idiomatic Clojure wrappers for Playwright's built-in HTTP client.
Supports all HTTP methods, form data, query params, custom headers, and
lifecycle management via with-api-context.
Usage: (require '[com.blockether.spel.core :as pw] '[com.blockether.spel.api :as api])
(core/with-playwright [playwright] (api/with-api-context [ctx (-> playwright api/api-request (api/new-api-context {:base-url "https://api.example.com"}))] (let [resp (api/get ctx "/users" {:params {:page 1 :limit 10}})] (println (api/response->map resp)))))
;; Response map: ;; {:status 200 ;; :status-text "OK" ;; :url "https://api.example.com/users?page=1&limit=10" ;; :ok? true ;; :headers {"content-type" "application/json"} ;; :body "{...}"}
All HTTP methods accept either a RequestOptions object or a Clojure map:
(api/post ctx "/users" {:headers {"Content-Type" "application/json"} :data "{"name": "Alice"}" :timeout 5000})
(api/put ctx "/users/1" {:form (api/map->form-data {:name "Bob" :email "bob@example.com"})})
(api/delete ctx "/users/1")
Playwright API testing — APIRequest, APIRequestContext, APIResponse.
Provides idiomatic Clojure wrappers for Playwright's built-in HTTP client.
Supports all HTTP methods, form data, query params, custom headers, and
lifecycle management via `with-api-context`.
Usage:
(require '[com.blockether.spel.core :as pw]
'[com.blockether.spel.api :as api])
(core/with-playwright [playwright]
(api/with-api-context [ctx (-> playwright api/api-request
(api/new-api-context {:base-url "https://api.example.com"}))]
(let [resp (api/get ctx "/users" {:params {:page 1 :limit 10}})]
(println (api/response->map resp)))))
;; Response map:
;; {:status 200
;; :status-text "OK"
;; :url "https://api.example.com/users?page=1&limit=10"
;; :ok? true
;; :headers {"content-type" "application/json"}
;; :body "{...}"}
All HTTP methods accept either a RequestOptions object or a Clojure map:
(api/post ctx "/users"
{:headers {"Content-Type" "application/json"}
:data "{\"name\": \"Alice\"}"
:timeout 5000})
(api/put ctx "/users/1"
{:form (api/map->form-data {:name "Bob" :email "bob@example.com"})})
(api/delete ctx "/users/1")Map of hook functions invoked during the API request lifecycle.
All hooks are optional (nil = no-op). Each hook receives context about the current operation and can observe or transform it.
Keys:
:on-request (fn [method url opts]) -> opts Called before every request. Receives the HTTP method keyword (:get, :post, ...), the URL string, and the opts map. Return value replaces opts. Return nil to keep original. Use for: logging, auth token injection, request transformation.
:on-response (fn [method url response]) -> response
Called after every successful request. Receives the method,
URL, and the APIResponse (or response map from request!).
Return value replaces the response. Return nil to keep original.
Use for: logging, metrics, response transformation.
:on-error (fn [method url anomaly]) -> anomaly Called when a request returns an anomaly (network error, timeout, etc.). Return value replaces the anomaly. Return nil to keep original. Use for: error logging, alerting, error transformation.
:on-retry (fn [{:keys [attempt max-attempts delay-ms result]}]) Called before each retry sleep. Side-effect only, return value ignored. Use for: logging retry attempts, metrics.
Examples:
;; Global logging (alter-var-root #'api/hooks (constantly {:on-request (fn [m url _] (println "→" m url)) :on-response (fn [m url r] (println "←" m url (.status r)) r)}))
;; Per-scope auth injection (binding [api/hooks {:on-request (fn [_ _ opts] (update opts :headers assoc "Authorization" (str "Bearer " (get-token))))}] (api-get ctx "/protected"))
Map of hook functions invoked during the API request lifecycle.
All hooks are optional (nil = no-op). Each hook receives context
about the current operation and can observe or transform it.
Keys:
:on-request (fn [method url opts]) -> opts
Called before every request. Receives the HTTP method keyword
(:get, :post, ...), the URL string, and the opts map.
Return value replaces opts. Return nil to keep original.
Use for: logging, auth token injection, request transformation.
:on-response (fn [method url response]) -> response
Called after every successful request. Receives the method,
URL, and the APIResponse (or response map from `request!`).
Return value replaces the response. Return nil to keep original.
Use for: logging, metrics, response transformation.
:on-error (fn [method url anomaly]) -> anomaly
Called when a request returns an anomaly (network error,
timeout, etc.). Return value replaces the anomaly.
Return nil to keep original.
Use for: error logging, alerting, error transformation.
:on-retry (fn [{:keys [attempt max-attempts delay-ms result]}])
Called before each retry sleep. Side-effect only, return
value ignored.
Use for: logging retry attempts, metrics.
Examples:
;; Global logging
(alter-var-root #'api/*hooks*
(constantly
{:on-request (fn [m url _] (println "→" m url))
:on-response (fn [m url r] (println "←" m url (.status r)) r)}))
;; Per-scope auth injection
(binding [api/*hooks* {:on-request (fn [_ _ opts]
(update opts :headers
assoc "Authorization" (str "Bearer " (get-token))))}]
(api-get ctx "/protected"))Function that encodes Clojure data to a JSON string. Bind to your preferred JSON library's encode function.
Must accept any Clojure data (maps, vectors, strings, numbers, etc.) and return a String.
Examples: ;; cheshire (binding [api/json-encoder cheshire.core/generate-string] (api/api-post ctx "/users" {:json {:name "Alice"}}))
;; data.json (binding [api/json-encoder clojure.data.json/write-str] (api/api-post ctx "/users" {:json {:name "Alice"}}))
;; jsonista (binding [api/json-encoder jsonista.core/write-value-as-string] (api/api-post ctx "/users" {:json {:name "Alice"}}))
;; Set globally for convenience (alter-var-root #'api/json-encoder (constantly cheshire.core/generate-string))
Function that encodes Clojure data to a JSON string.
Bind to your preferred JSON library's encode function.
Must accept any Clojure data (maps, vectors, strings, numbers, etc.)
and return a String.
Examples:
;; cheshire
(binding [api/*json-encoder* cheshire.core/generate-string]
(api/api-post ctx "/users" {:json {:name "Alice"}}))
;; data.json
(binding [api/*json-encoder* clojure.data.json/write-str]
(api/api-post ctx "/users" {:json {:name "Alice"}}))
;; jsonista
(binding [api/*json-encoder* jsonista.core/write-value-as-string]
(api/api-post ctx "/users" {:json {:name "Alice"}}))
;; Set globally for convenience
(alter-var-root #'api/*json-encoder* (constantly cheshire.core/generate-string))(api-delete ctx url)(api-delete ctx url opts)Sends a DELETE request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Sends a DELETE request. Params: `ctx` - APIRequestContext instance. `url` - String. URL path. `opts` - RequestOptions or map, optional. See `request-options`. Returns: APIResponse or anomaly map.
(api-dispose! ctx)Disposes the APIRequestContext and all responses.
Params:
ctx - APIRequestContext instance.
Returns: nil.
Disposes the APIRequestContext and all responses. Params: `ctx` - APIRequestContext instance. Returns: nil.
(api-fetch ctx url)(api-fetch ctx url opts)Sends a request with custom method (set via :method in opts).
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Examples: (api-fetch ctx "/resource" {:method "OPTIONS"})
Sends a request with custom method (set via :method in opts).
Params:
`ctx` - APIRequestContext instance.
`url` - String. URL path.
`opts` - RequestOptions or map, optional. See `request-options`.
Returns:
APIResponse or anomaly map.
Examples:
(api-fetch ctx "/resource" {:method "OPTIONS"})(api-get ctx url)(api-get ctx url opts)Sends a GET request.
Params:
ctx - APIRequestContext instance.
url - String. URL path (resolved against base-url if set).
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Examples: (api-get ctx "/users") (api-get ctx "/users" {:params {:page 1 :limit 10} :headers {"Authorization" "Bearer token"}})
Sends a GET request.
Params:
`ctx` - APIRequestContext instance.
`url` - String. URL path (resolved against base-url if set).
`opts` - RequestOptions or map, optional. See `request-options`.
Returns:
APIResponse or anomaly map.
Examples:
(api-get ctx "/users")
(api-get ctx "/users" {:params {:page 1 :limit 10}
:headers {"Authorization" "Bearer token"}})(api-head ctx url)(api-head ctx url opts)Sends a HEAD request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Sends a HEAD request. Params: `ctx` - APIRequestContext instance. `url` - String. URL path. `opts` - RequestOptions or map, optional. See `request-options`. Returns: APIResponse or anomaly map.
(api-patch ctx url)(api-patch ctx url opts)Sends a PATCH request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Sends a PATCH request. Params: `ctx` - APIRequestContext instance. `url` - String. URL path. `opts` - RequestOptions or map, optional. See `request-options`. Returns: APIResponse or anomaly map.
(api-post ctx url)(api-post ctx url opts)Sends a POST request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Examples: (api-post ctx "/users" {:data "{"name": "Alice"}" :headers {"Content-Type" "application/json"}}) (api-post ctx "/login" {:form (map->form-data {:user "admin" :pass "secret"})})
Sends a POST request.
Params:
`ctx` - APIRequestContext instance.
`url` - String. URL path.
`opts` - RequestOptions or map, optional. See `request-options`.
Returns:
APIResponse or anomaly map.
Examples:
(api-post ctx "/users" {:data "{\"name\": \"Alice\"}"
:headers {"Content-Type" "application/json"}})
(api-post ctx "/login" {:form (map->form-data {:user "admin" :pass "secret"})})(api-put ctx url)(api-put ctx url opts)Sends a PUT request.
Params:
ctx - APIRequestContext instance.
url - String. URL path.
opts - RequestOptions or map, optional. See request-options.
Returns: APIResponse or anomaly map.
Sends a PUT request. Params: `ctx` - APIRequestContext instance. `url` - String. URL path. `opts` - RequestOptions or map, optional. See `request-options`. Returns: APIResponse or anomaly map.
(api-request pw)Returns the APIRequest for the Playwright instance.
Params:
pw - Playwright instance.
Returns: APIRequest instance.
Returns the APIRequest for the Playwright instance. Params: `pw` - Playwright instance. Returns: APIRequest instance.
(api-response->map resp)Converts an APIResponse to a Clojure map.
Reads the response body as text and includes all metadata. Useful for logging, debugging, and test assertions.
Params:
resp - APIResponse instance.
Returns: Map with keys: :status - Long. HTTP status code. :status-text - String. HTTP status text. :url - String. Response URL. :ok? - Boolean. True if status is 2xx. :headers - Map. Response headers. :body - String. Response body text (nil on read failure).
Examples: (let [resp (api-get ctx "/users")] (api-response->map resp)) ;; => {:status 200 ;; :status-text "OK" ;; :url "https://api.example.com/users" ;; :ok? true ;; :headers {"content-type" "application/json"} ;; :body "{"users": [...]}"}
Converts an APIResponse to a Clojure map.
Reads the response body as text and includes all metadata.
Useful for logging, debugging, and test assertions.
Params:
`resp` - APIResponse instance.
Returns:
Map with keys:
:status - Long. HTTP status code.
:status-text - String. HTTP status text.
:url - String. Response URL.
:ok? - Boolean. True if status is 2xx.
:headers - Map. Response headers.
:body - String. Response body text (nil on read failure).
Examples:
(let [resp (api-get ctx "/users")]
(api-response->map resp))
;; => {:status 200
;; :status-text "OK"
;; :url "https://api.example.com/users"
;; :ok? true
;; :headers {"content-type" "application/json"}
;; :body "{\"users\": [...]}"}(api-response-body resp)Returns the response body as bytes. Passes through anomaly maps unchanged.
Returns the response body as bytes. Passes through anomaly maps unchanged.
(api-response-dispose! resp)Disposes the APIResponse. No-op on anomaly maps.
Params:
resp - APIResponse instance.
Returns: nil.
Disposes the APIResponse. No-op on anomaly maps. Params: `resp` - APIResponse instance. Returns: nil.
(api-response-headers resp)Returns the response headers. Passes through anomaly maps unchanged.
Returns the response headers. Passes through anomaly maps unchanged.
(api-response-headers-array resp)Returns the response headers as a vector of {:name :value} maps.
Preserves duplicate headers (unlike api-response-headers which
keeps only the last value for each name).
Passes through anomaly maps unchanged.
Params:
resp - APIResponse instance.
Returns: Vector of maps, or anomaly map.
Returns the response headers as a vector of {:name :value} maps.
Preserves duplicate headers (unlike `api-response-headers` which
keeps only the last value for each name).
Passes through anomaly maps unchanged.
Params:
`resp` - APIResponse instance.
Returns:
Vector of maps, or anomaly map.(api-response-ok? resp)Returns whether the response is OK (2xx). Passes through anomaly maps unchanged.
Returns whether the response is OK (2xx). Passes through anomaly maps unchanged.
(api-response-status resp)Returns the HTTP status code. Passes through anomaly maps unchanged.
Returns the HTTP status code. Passes through anomaly maps unchanged.
(api-response-status-text resp)Returns the HTTP status text. Passes through anomaly maps unchanged.
Returns the HTTP status text. Passes through anomaly maps unchanged.
(api-response-text resp)Returns the response body as text. Passes through anomaly maps unchanged.
Returns the response body as text. Passes through anomaly maps unchanged.
(api-response-url resp)Returns the response URL. Passes through anomaly maps unchanged.
Returns the response URL. Passes through anomaly maps unchanged.
Default retry configuration.
:max-attempts - Total attempts including the first (default: 3). :delay-ms - Initial delay between retries in ms (default: 200). :backoff - Backoff strategy (default: :exponential). :fixed — constant delay. :linear — delay * attempt. :exponential — delay * 2^attempt. :max-delay-ms - Ceiling on delay (default: 10000). :retry-when - Predicate (fn [result] -> truthy to retry). Default: retries on anomalies and 5xx status codes.
Default retry configuration.
:max-attempts - Total attempts including the first (default: 3).
:delay-ms - Initial delay between retries in ms (default: 200).
:backoff - Backoff strategy (default: :exponential).
:fixed — constant delay.
:linear — delay * attempt.
:exponential — delay * 2^attempt.
:max-delay-ms - Ceiling on delay (default: 10000).
:retry-when - Predicate (fn [result] -> truthy to retry).
Default: retries on anomalies and 5xx status codes.(fd-append fd name value)Appends a field to FormData.
Params:
fd - FormData instance.
name - String. Field name.
value - String. Field value.
Returns: FormData instance.
Appends a field to FormData. Params: `fd` - FormData instance. `name` - String. Field name. `value` - String. Field value. Returns: FormData instance.
(fd-set fd name value)Sets a field in FormData.
Params:
fd - FormData instance.
name - String. Field name.
value - String. Field value.
Returns: FormData instance.
Sets a field in FormData. Params: `fd` - FormData instance. `name` - String. Field name. `value` - String. Field value. Returns: FormData instance.
(form-data)Creates a new FormData instance.
Returns: FormData instance.
Creates a new FormData instance. Returns: FormData instance.
(map->form-data m)Converts a Clojure map to FormData.
Params:
m - Map of string->string.
Returns: FormData instance.
Converts a Clojure map to FormData. Params: `m` - Map of string->string. Returns: FormData instance.
(new-api-context api-req)(new-api-context api-req opts)Creates a new APIRequestContext.
Params:
api-req - APIRequest instance (from api-request).
opts - Map, optional. Context options:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
:timeout - Double. Default timeout in ms (default: 30000).
:user-agent - String. User-Agent header.
:max-redirects - Long. Max redirects to follow (default: 20).
:fail-on-status-code - Boolean. Throw on non-2xx/3xx status.
:storage-state - String. Storage state JSON or path.
Returns: APIRequestContext or anomaly map.
Examples: (new-api-context (api-request pw) {:base-url "https://api.example.com" :extra-http-headers {"Authorization" "Bearer token"} :timeout 10000})
Creates a new APIRequestContext.
Params:
`api-req` - APIRequest instance (from `api-request`).
`opts` - Map, optional. Context options:
:base-url - String. Base URL for all requests.
:extra-http-headers - Map. Headers sent with every request.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
:timeout - Double. Default timeout in ms (default: 30000).
:user-agent - String. User-Agent header.
:max-redirects - Long. Max redirects to follow (default: 20).
:fail-on-status-code - Boolean. Throw on non-2xx/3xx status.
:storage-state - String. Storage state JSON or path.
Returns:
APIRequestContext or anomaly map.
Examples:
(new-api-context (api-request pw)
{:base-url "https://api.example.com"
:extra-http-headers {"Authorization" "Bearer token"}
:timeout 10000})(request! pw method url)(request! pw method url opts)Fire-and-forget HTTP request. Creates an ephemeral context, makes the request, reads the full response into a Clojure map, and disposes everything. No context management needed.
Params:
pw - Playwright instance.
method - Keyword. :get :post :put :patch :delete :head.
url - String. Full URL (not relative — no base-url).
opts - Map, optional. See request-options for all keys.
Returns:
Response map (same shape as api-response->map) or anomaly map.
Examples: ;; Simple GET — no setup, no cleanup (request! pw :get "https://api.example.com/health") ;; => {:status 200 :ok? true :body "OK" ...}
;; POST with JSON body (request! pw :post "https://api.example.com/users" {:data "{"name": "Alice"}" :headers {"Content-Type" "application/json" "Authorization" "Bearer token"}})
;; Hit multiple domains without any context setup (let [users (request! pw :get "https://users.example.com/me" {:headers {"Authorization" "Bearer user-token"}}) invoices (request! pw :get "https://billing.example.com/invoices" {:headers {"X-API-Key" "billing-key"}})] [users invoices])
Fire-and-forget HTTP request. Creates an ephemeral context, makes the
request, reads the full response into a Clojure map, and disposes
everything. No context management needed.
Params:
`pw` - Playwright instance.
`method` - Keyword. :get :post :put :patch :delete :head.
`url` - String. Full URL (not relative — no base-url).
`opts` - Map, optional. See `request-options` for all keys.
Returns:
Response map (same shape as `api-response->map`) or anomaly map.
Examples:
;; Simple GET — no setup, no cleanup
(request! pw :get "https://api.example.com/health")
;; => {:status 200 :ok? true :body "OK" ...}
;; POST with JSON body
(request! pw :post "https://api.example.com/users"
{:data "{\"name\": \"Alice\"}"
:headers {"Content-Type" "application/json"
"Authorization" "Bearer token"}})
;; Hit multiple domains without any context setup
(let [users (request! pw :get "https://users.example.com/me"
{:headers {"Authorization" "Bearer user-token"}})
invoices (request! pw :get "https://billing.example.com/invoices"
{:headers {"X-API-Key" "billing-key"}})]
[users invoices])(request-options opts)Creates RequestOptions from a map.
Params:
opts - Map with optional:
:method - String. HTTP method override.
:headers - Map. HTTP headers ({"Authorization" "Bearer ..."}).
:data - String, bytes, or Object. Request body.
Objects are auto-serialized to JSON by Playwright.
:json - Any Clojure data. Encoded via *json-encoder*,
sets Content-Type to application/json automatically.
Mutually exclusive with :data, :form, :multipart.
:form - FormData. URL-encoded form data.
:multipart - FormData. Multipart form data (file uploads).
:timeout - Double. Timeout in ms (default: 30000).
:params - Map. Query parameters.
:max-redirects - Long. Max redirects to follow (default: 20).
:max-retries - Long. Retry network errors (default: 0).
:fail-on-status-code - Boolean. Throw on non-2xx/3xx status.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
The :json key requires *json-encoder* to be bound:
(binding [api/json-encoder cheshire.core/generate-string] (api/api-post ctx "/users" {:json {:name "Alice" :age 30}}))
Returns: RequestOptions instance.
Creates RequestOptions from a map.
Params:
`opts` - Map with optional:
:method - String. HTTP method override.
:headers - Map. HTTP headers ({"Authorization" "Bearer ..."}).
:data - String, bytes, or Object. Request body.
Objects are auto-serialized to JSON by Playwright.
:json - Any Clojure data. Encoded via `*json-encoder*`,
sets Content-Type to application/json automatically.
Mutually exclusive with :data, :form, :multipart.
:form - FormData. URL-encoded form data.
:multipart - FormData. Multipart form data (file uploads).
:timeout - Double. Timeout in ms (default: 30000).
:params - Map. Query parameters.
:max-redirects - Long. Max redirects to follow (default: 20).
:max-retries - Long. Retry network errors (default: 0).
:fail-on-status-code - Boolean. Throw on non-2xx/3xx status.
:ignore-https-errors - Boolean. Ignore SSL certificate errors.
The :json key requires `*json-encoder*` to be bound:
(binding [api/*json-encoder* cheshire.core/generate-string]
(api/api-post ctx "/users" {:json {:name "Alice" :age 30}}))
Returns:
RequestOptions instance.(retry f)(retry f opts)Execute f (a no-arg function) with retry logic.
Params:
f - No-arg function that returns a result.
opts - Map, optional. Override keys from default-retry-opts:
:max-attempts - Total attempts (default: 3).
:delay-ms - Initial delay in ms (default: 200).
:backoff - :fixed, :linear, or :exponential (default).
:max-delay-ms - Max delay ceiling in ms (default: 10000).
:retry-when - (fn [result]) → truthy to retry.
Returns: The result of the last attempt.
Examples: ;; Retry a flaky endpoint (retry #(api-get ctx "/flaky"))
;; Custom: retry on 429 Too Many Requests with linear backoff (retry #(api-get ctx "/rate-limited") {:max-attempts 5 :delay-ms 1000 :backoff :linear :retry-when (fn [r] (= 429 (:status (api-response->map r))))})
Execute `f` (a no-arg function) with retry logic.
Params:
`f` - No-arg function that returns a result.
`opts` - Map, optional. Override keys from `default-retry-opts`:
:max-attempts - Total attempts (default: 3).
:delay-ms - Initial delay in ms (default: 200).
:backoff - :fixed, :linear, or :exponential (default).
:max-delay-ms - Max delay ceiling in ms (default: 10000).
:retry-when - (fn [result]) → truthy to retry.
Returns:
The result of the last attempt.
Examples:
;; Retry a flaky endpoint
(retry #(api-get ctx "/flaky"))
;; Custom: retry on 429 Too Many Requests with linear backoff
(retry #(api-get ctx "/rate-limited")
{:max-attempts 5
:delay-ms 1000
:backoff :linear
:retry-when (fn [r] (= 429 (:status (api-response->map r))))})(with-api-context [sym expr] & body)Binds a single APIRequestContext and ensures disposal.
Usage: (with-api-context [ctx (new-api-context (api-request pw) {:base-url "https://api.example.com"})] (api-get ctx "/users"))
Binds a single APIRequestContext and ensures disposal.
Usage:
(with-api-context [ctx (new-api-context (api-request pw) {:base-url "https://api.example.com"})]
(api-get ctx "/users"))(with-api-contexts bindings & body)Binds multiple APIRequestContexts and disposes all on exit.
Same shape as with-open — flat pairs of [name expr].
Usage: (with-api-contexts [users (new-api-context (api-request pw) {:base-url "https://users.example.com" :extra-http-headers {"Authorization" "Bearer token"}}) billing (new-api-context (api-request pw) {:base-url "https://billing.example.com" :extra-http-headers {"X-API-Key" "key"}}) public (new-api-context (api-request pw) {:base-url "https://public.example.com"})] (api-get users "/me") (api-get billing "/invoices") (api-get public "/catalog"))
Binds multiple APIRequestContexts and disposes all on exit.
Same shape as `with-open` — flat pairs of [name expr].
Usage:
(with-api-contexts [users (new-api-context (api-request pw)
{:base-url "https://users.example.com"
:extra-http-headers {"Authorization" "Bearer token"}})
billing (new-api-context (api-request pw)
{:base-url "https://billing.example.com"
:extra-http-headers {"X-API-Key" "key"}})
public (new-api-context (api-request pw)
{:base-url "https://public.example.com"})]
(api-get users "/me")
(api-get billing "/invoices")
(api-get public "/catalog"))(with-hooks hooks & body)Execute body with the given hooks merged into *hooks*.
Merges with (not replaces) any existing hooks so outer bindings are preserved for keys you don't override.
Usage: (with-hooks {:on-request (fn [m url opts] (log/info m url) opts) :on-response (fn [m url resp] (metrics/inc! :api-calls) resp)} (api-get ctx "/users") (api-post ctx "/users" {:json {:name "Alice"}}))
Execute body with the given hooks merged into `*hooks*`.
Merges with (not replaces) any existing hooks so outer bindings
are preserved for keys you don't override.
Usage:
(with-hooks {:on-request (fn [m url opts] (log/info m url) opts)
:on-response (fn [m url resp] (metrics/inc! :api-calls) resp)}
(api-get ctx "/users")
(api-post ctx "/users" {:json {:name "Alice"}}))(with-retry opts-or-body & body)Execute body with retry logic.
Usage: ;; Default: 3 attempts, exponential backoff, retry on anomalies + 5xx (with-retry {} (api-get ctx "/flaky-endpoint"))
;; Custom retry config (with-retry {:max-attempts 5 :delay-ms 500 :backoff :linear :retry-when (fn [r] (and (map? r) (>= (:status r) 500)))} (api-post ctx "/idempotent-endpoint" {:json {:action "process"}}))
;; Retry standalone requests too (with-retry {:max-attempts 3} (request! pw :get "https://api.example.com/health"))
Execute body with retry logic.
Usage:
;; Default: 3 attempts, exponential backoff, retry on anomalies + 5xx
(with-retry {}
(api-get ctx "/flaky-endpoint"))
;; Custom retry config
(with-retry {:max-attempts 5
:delay-ms 500
:backoff :linear
:retry-when (fn [r] (and (map? r) (>= (:status r) 500)))}
(api-post ctx "/idempotent-endpoint"
{:json {:action "process"}}))
;; Retry standalone requests too
(with-retry {:max-attempts 3}
(request! pw :get "https://api.example.com/health"))cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |