Client
- Declaring client procedures
- Creating a client
- Calling an endpoint
- Return types
- Error handling
- Connection handling
- Overloading
Presto ships with an HTTP client that is generated from your procedure
signatures. You describe what a request looks like (its endpoint, method, and
parameters) and the rest macro writes the
code that builds the request, sends it, follows redirects, and decodes the
response into a Nim value. The client lives in the
presto/client module.
Declaring client procedures
Annotate a procedure with {.rest.} and describe the request with pragmas. The
proc must have no body; the macro supplies one.
proc getUser(id: int): string {.rest, endpoint: "/users/{id}".}
proc createUser(body: string): string {.
rest, endpoint: "/users", meth: MethodPost.}
Parameter names drive how each argument is used:
- a name that matches a
{pattern}inendpointfills that path segment; - an argument named
body(or starting withbody) becomes the request body — only allowed forPOST/PUT/PATCH/DELETE; - any other argument becomes a query-string parameter.
Option[T]makes it optional;seq[T]repeats the key.
Client-side values are converted with your encodeString (path/query) and
encodeBytes (body) procedures, and responses are decoded with decodeBytes.
See the encode/decode contract.
Pragmas
| Pragma | Purpose |
|---|---|
endpoint : "/path/{x}" | request path, with optional patterns |
meth : MethodGet | HTTP method (defaults to GET) |
accept : "application/json" | value of the Accept header (defaults to application/json) |
connection : {Dedicated} | connection handling (see Connection handling) |
metrics / metricsTypes : {...} | enable client metrics for the call (see CORS and metrics) |
Creating a client
RestClientRef.new can be constructed from a
TransportAddress, from an HttpAddress, or from a URL string. The URL form
returns a Result because it resolves the host up front.
# from a transport address
let client = RestClientRef.new(initTAddress("127.0.0.1:8080"))
# from a URL (returns a Result)
let client2 = RestClientRef.new("http://api.example.com/").get()
# with flags
let client3 = RestClientRef.new(
initTAddress("127.0.0.1:8080"),
HttpClientScheme.NonSecure,
flags = {RestClientFlag.CommaSeparatedArray})
Two RestClientFlags tune behavior:
CommaSeparatedArray— encodeseq[T]query parameters as a single comma-delimited value instead of repeating the key.ResolveAlways— perform DNS resolution on every request rather than caching the resolved address.
Calling an endpoint
The generated proc is async and takes your declared parameters plus three
extra keyword arguments: restContentType, restAcceptType, and
extraHeaders.
let client = RestClientRef.new(initTAddress("127.0.0.1:8080"))
let user = await client.getUser(42)
let created = await client.createUser(
body = "{\"name\":\"Ada\"}",
restContentType = "application/json",
extraHeaders = @[("Authorization", "Bearer secret")])
await client.closeWait()
restAcceptType overrides the accept pragma at the call site and understands
quality weights, e.g. "app/type1;q=1.0,app/type2;q=0.1".
Return types
The proc's return type selects how much of the response you get back and how errors are handled.
| Return type | Result |
|---|---|
a value type T (e.g. string, int, a custom type) | the decoded body; a non-2xx status raises RestResponseError |
RestStatus | just the HTTP status code; the body is consumed |
RestPlainResponse | status, content type, headers, and raw data: seq[byte] |
RestResponse[T] | status, content type, and the decoded data: T |
RestHttpResponseRef | the raw response for manual/streaming reads |
Examples:
proc getStatus(): RestStatus {.rest, endpoint: "/health".}
proc getRaw(): RestPlainResponse {.rest, endpoint: "/blob".}
proc getTyped(): RestResponse[int] {.rest, endpoint: "/count".}
proc getStream(body: string): RestHttpResponseRef {.
rest, endpoint: "/download", meth: MethodPost, accept: "*/*".}
Reading a streaming response:
let resp = await client.getStream("query")
let reader = resp.getBodyReader()
let chunk = await reader.read()
await reader.closeWait()
await resp.closeWait()
Error handling
When a value-returning proc receives a non-2xx status it raises
RestResponseError, which carries
the details of the failed response.
try:
let user = await client.getUser(99999)
discard user
except RestResponseError as exc:
echo exc.status # e.g. 404
echo exc.message # response body as text
echo exc.contentType
Other exceptions the generated procs may raise include RestEncodingError
(a parameter or body failed to encode), RestDnsResolveError (host resolution
failed), RestCommunicationError (transport/HTTP failure), and
RestDecodingError (the response body failed to decode). CancelledError
propagates as usual.
Connection handling
By default connections are pooled and reused. The
connection pragma changes this per
call:
{}or{Dedicated}— keep the connection open for reuse.{Close}— close the connection after the request.
proc oneShot(): RestPlainResponse {.
rest, endpoint: "/once", connection: {Close}.}
Overloading
Because the macro produces ordinary procedures, you can overload the same endpoint with different parameter sets, e.g. a queryless variant and one that takes filters, and let Nim's overload resolution pick between them at the call site.