Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Client

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} in endpoint fills that path segment;
  • an argument named body (or starting with body) becomes the request body — only allowed for POST/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

PragmaPurpose
endpoint : "/path/{x}"request path, with optional patterns
meth : MethodGetHTTP 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 — encode seq[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 typeResult
a value type T (e.g. string, int, a custom type)the decoded body; a non-2xx status raises RestResponseError
RestStatusjust the HTTP status code; the body is consumed
RestPlainResponsestatus, content type, headers, and raw data: seq[byte]
RestResponse[T]status, content type, and the decoded data: T
RestHttpResponseRefthe 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.