Skip to content

Named And Typed Clients

Chris Michael edited this page Oct 6, 2026 · 1 revision

Pudu

Named And Typed Clients

Named clients put each client's configuration in one place. Builders of the same name are combined in order, and the defaults' builders apply to every name first. A name never configured gets the defaults. A typed client is any record wrapping a client: Factory.typed makes one from a named client and a constructor.

module Ex04Factory

import Std.Concurrent.Cancel as Cancel
import Std.Http as Http
import Std.Io as Io
import Std.Result as Result
import PuduLangHttpClient as HttpClient
import PuduLangHttpClient.Client as Client
import PuduLangHttpClient.Factory as Factory
import PuduLangHttpClient.Factory.Builder as Builder
import PuduLangHttpClient.Request as Request
import PuduLangHttpClient.Response as Response
import PuduLangHttpClient.Stub as Stub

type Weather = { client: Client.Client }

fn today(weather: &Weather, city: Str) -> HttpClient.Outcome[Str] { Client.getText(&weather.client, "today?city=" + city) }

export fn main() -> Int {
  let stub = Stub.create()
  Stub.on(&stub, Http.Get, "/today", |request: Request.Request| Ok(Response.answer(&request, 200).withText("sunny for " + show(Request.header(&request, "x-app")))))
  let factory = match Factory.build([
      Builder.defaults().withDefaultHeader("x-app", "ex04").withPrimary(|| Stub.send(&stub)),
      Builder.named("weather").withBaseAddress("https://weather.test/"),
      Builder.named("weather").withTimeout(3000)
    ]) {
    case Ok(built) => built
    case Err(invalid) => panic(Factory.explain(&invalid))
  }
  let weather = Factory.typed(&factory, "weather", |client: Client.Client| Weather{client: client})
  let forecast = today(&weather, "Lima")
  let handler = Factory.createHandler(&factory, "weather")
  let direct = Result.map(handler(Request.get("https://weather.test/today"), Cancel.token()), |r: Response.Response| r.status.code)
  let rotating = Factory.rotatingHandler(&factory, "weather")
  let again = Result.map(rotating(Request.get("https://weather.test/today"), Cancel.token()), |r: Response.Response| r.status.code)
  let invalid = Factory.build([Builder.named("broken").withBaseAddress("weather.test").withTimeout(0)])
  let _a = Io.writeLine(show(forecast) + " " + show(weather.client.timeout))
  let _b = Io.writeLine(show((direct, again)) + " " + show(Factory.names(&factory)))
  let _c = Io.writeLine(match invalid {
      case Ok(_) => "built"
      case Err(problems) => Factory.explain(&problems)
    })
  if forecast == Ok("sunny for Some(\"ex04\")") && weather.client.timeout == 3000 && direct == Ok(200) && Result.isErr(&invalid) { 0 } else { 1 }
}

Check it and run it:

pudu check src/Ex04Factory.pudu
pudu run src/Ex04Factory.pudu

Output:

Ok("sunny for Some(\"ex04\")") 3000
(Ok(200), Ok(200)) ["weather"]
The client factory is invalid: client 'broken': baseAddress must be an absolute http or https address: weather.test; client 'broken': timeout must be positive or INFINITE

The builder methods are withBaseAddress, withDefaultHeader, withTimeout, withMaxResponseContentBufferSize, withDefaultVersion, and configureClient for the client; withHandler, withHandlerFactory, and configureHandlers for delegating handlers; withTransport, configureTransport, and withPrimary for the primary handler; withHandlerLifetime; and observedBy, withoutObservers, redactingHeaders, and redactingWhen for observers.

Factory.createHandler answers the name's current pipeline as a sender, for code that builds its own client or wraps the pipeline in a handler of its own; Factory.rotatingHandler answers a sender that asks for the current pipeline on every request, for code that holds it for a long time. Factory.names lists the names configured on their own.

Related

Clone this wiki locally