Repository navigation
Endpoints And JSON
Chris Michael edited this page Oct 6, 2026
·
1 revision
An endpoint is a method and an address template whose :name slots are filled from arguments,
percent-encoded, when it is called — the same slot syntax as the standard router. Endpoint.call
reads a value that derives Json.Decode; callWith also writes one that derives Json.Encode.
Client.Json offers the same reading and writing on plain addresses.
module Ex10Endpoints
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.Client as Client
import PuduLangHttpClient.Client.Json as JsonClient
import PuduLangHttpClient.Content as Content
import PuduLangHttpClient.Endpoint as Endpoint
import PuduLangHttpClient.Request as Request
import PuduLangHttpClient.Response as Response
import PuduLangHttpClient.Stub as Stub
type Book = { isbn: Str, title: Str, pages: Int } derives Json.Encode, Json.Decode
const FIND: Endpoint.Endpoint = Endpoint.Endpoint{method: Http.Get, template: "books/:isbn"}
const SAVE: Endpoint.Endpoint = Endpoint.Endpoint{method: Http.Put, template: "books/:isbn"}
fn library() -> Stub.Stub {
let stub = Stub.create()
Stub.when(&stub, |request: Request.Request| request.uri.contains("/books/"), fn(request: Request.Request) -> HttpClient.Outcome[Response.Response] {
match request.content {
case Some(body) => Ok(Response.answer(&request, 200).withContent(body))
case None => Ok(Response.answer(&request, 200).withJson(&Json.object(&[("isbn", Json.Text("978-1")), ("title", Json.Text("Pudu")), ("pages", Json.Number(320))])))
}
})
stub
}
export fn main() -> Int {
let client = Client.create(Stub.send(&library())).withBaseAddress("https://library.test/")
let found: HttpClient.Outcome[Book] = Endpoint.call(&client, &FIND, &[("isbn", "978-1")])
let saved: HttpClient.Outcome[Book] = Endpoint.callWith(&client, &SAVE, &[("isbn", "978-2")], &Book{isbn: "978-2", title: "Deer", pages: 12})
let viaJson: HttpClient.Outcome[Book] = JsonClient.getFromJson(&client, "books/978-1")
let posted = Result.andThen(JsonClient.putAsJson(&client, "books/978-3", &Book{isbn: "978-3", title: "Moss", pages: 3}), |r: Response.Response| Response.readAs[Book](&r))
let unfilled = Endpoint.fill(&FIND, &[])
let _a = Io.writeLine(show(found) + "\n" + show(saved) + "\n" + show(viaJson) + "\n" + show(posted) + "\n" + show(unfilled))
if found == viaJson && Result.isOk(&saved) && Result.isOk(&posted) && Result.isErr(&unfilled) { 0 } else { 1 }
}
Check it and run it:
pudu check src/Ex10Endpoints.pudu
pudu run src/Ex10Endpoints.puduOutput:
Ok(Book{isbn: "978-1", title: "Pudu", pages: 320})
Ok(Book{isbn: "978-2", title: "Deer", pages: 12})
Ok(Book{isbn: "978-1", title: "Pudu", pages: 320})
Ok(Book{isbn: "978-3", title: "Moss", pages: 3})
Err(InvalidRequest("the template slot 'isbn' has no argument"))
Endpoint.fill refuses a slot without an argument, and a colon that does not start a name (:8080,
a:/) is kept as written. Client.Json has getJson, getFromJson, deleteFromJson,
postAsJson, putAsJson, and patchAsJson; every helper asks for application/json, and a
failed status is never decoded.
- Previous: Logging, Propagation, And Metrics
- Next: Testing With Stubs
- Reference Index