Repository navigation
Requests And Responses
A Request is a method, an address, a version with a policy, headers, an optional body, typed
options for handlers, and an optional receiver for a streamed response. Requests are built one
decision at a time with the Request.Shaping methods (withHeader, setHeader, removeHeader,
withContent, withVersion, withOption, withReceiver, withBearer, accepting).
Bodies come from Content: plain, text, bytes, octets, json, jsonOf for any value that
derives Json.Encode, form, multipart and multipartWith with field and file parts, and
streamed for a producer sent in chunks. Responses carry the status, version, headers, body,
trailers, and the final request after redirects; Response.text, bytes, json, and readAs read
the body, and ensureSuccess turns a status outside 2xx into a failure. This program sends through
a stub primary handler so it runs without a network:
module Ex02Messages
import Std.Concurrent.Cancel as Cancel
import Std.Http as Http
import Std.Io as Io
import Std.Json as Json
import Std.Result as Result
import PuduLangHttpClient as HttpClient
import PuduLangHttpClient.Content as Content
import PuduLangHttpClient.Domain.Version as Version
import PuduLangHttpClient.Options as Options
import PuduLangHttpClient.Request as Request
import PuduLangHttpClient.Response as Response
import PuduLangHttpClient.Stub as Stub
type Order = { sku: Str, quantity: Int } derives Json.Encode, Json.Decode
export fn main() -> Int {
let tenant = Options.textKey("tenant")
let request = Request.post("https://shop.test/orders", Content.jsonOf(&Order{sku: "tea", quantity: 2}))
.withHeader("x-request-id", "r-1")
.withBearer("t0k3n")
.accepting("application/json")
.withVersion(Http.Http11, Version.OrLower)
.withOption(&tenant, "acme")
let stub = Stub.create()
Stub.on(&stub, Http.Post, "/orders", fn(received: Request.Request) -> HttpClient.Outcome[Response.Response] {
let body = match received.content {
case Some(found) => found
case None => Content.empty()
}
Ok(Response.answer(&received, 201).withHeader("location", "/orders/9").withContent(body))
})
let sent = Stub.send(&stub)(request, Cancel.token())
let created = Result.andThen(sent, |response: Response.Response| Response.readAs[Order](&response))
let located = Result.map(sent, |response: Response.Response| Response.header(&response, "Location"))
let ensured = Result.andThen(Stub.send(&stub)(Request.get("https://shop.test/missing"), Cancel.token()), Response.ensureSuccess)
let _a = Io.writeLine(show(created) + " at " + show(located))
let _b = Io.writeLine(show(Request.option(&request, &tenant)) + " " + show(Request.header(&request, "content-type")))
let _c = Io.writeLine(show(Result.map(ensured, |r: Response.Response| r.status.code)))
if created == Ok(Order{sku: "tea", quantity: 2}) && ensured == Err(HttpClient.Unsuccessful(404, "Not Found")) { 0 } else { 1 }
}
Check it and run it:
pudu check src/Ex02Messages.pudu
pudu run src/Ex02Messages.puduOutput:
Ok(Order{sku: "tea", quantity: 2}) at Ok(Some("/orders/9"))
Some("acme") Some("application/json; charset=utf-8")
Err(Unsuccessful(404, "Not Found"))
Options.textKey, intKey, boolKey, and key make typed keys whose values ride on the request
for handlers to read with Request.option. Request.header and Response.header look among the
message's own headers and then its body's. The Failure variants are InvalidRequest,
NameResolution, Connection, SecureConnection, ProxyTunnel, Protocol, ResponseEnded,
ContentTooLarge, HeadersTooLarge, TooManyRedirects, NotPermitted, VersionUnsupported,
TimedOut, Cancelled, Unsuccessful, Unreadable, Rejected, and Crashed; isTransient,
isTimeout, isCancellation, and statusOf classify them.
- Previous: Getting Started
- Next: Clients
- Reference Index