Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-httpclient

Named HTTP clients for Pudu: a client factory, delegating handlers, pooled keep-alive connections, handler lifetimes, logging, and resilience

0.1.1Apache-2.01

InstallClose

Builder.pudu

Pudu189 lines9.8 KB

GitHub ↗
1/** @Factory.Builder.Module — how one named client and its handler pipeline are configured */2module PuduLangHttpClient.Factory.Builder34import Std.Http as Http5import Std.Option as Option6import PuduLangHttpClient.Client as Client7import PuduLangHttpClient.Constants.Defaults as Defaults8import PuduLangHttpClient.Domain.Version as Version9import PuduLangHttpClient.Handler as Handler10import PuduLangHttpClient.Transport as Transport1112/** @Factory.Builder.Primary — the innermost handler: a pooled transport, or one of the program's own */13export type Primary = Pooled(Transport.Options) | Custom(fn() -> Handler.Send)1415/** @Factory.Builder.Observation — what an observer is told about the client it watches */16export type Observation = { name: Str, redact: fn(Str) -> Bool }1718/** @Factory.Builder.Observer — a pair of handlers placed around and inside every other handler */19export type Observer = { outer: fn(Observation) -> Handler.Handler, inner: fn(Observation) -> Handler.Handler }2021/** @Factory.Builder.Builder — the configuration of one name, or of every client */22export type Builder = {23  name: Str,24  everyClient: Bool,25  clientActions: Array[fn(Client.Client) -> Client.Client],26  handlers: Array[fn() -> Handler.Handler],27  handlerEdits: Array[fn(Array[Handler.Handler]) -> Array[Handler.Handler]],28  primary: Option[Primary],29  transportEdits: Array[fn(Transport.Options) -> Transport.Options],30  lifetime: Option[Int],31  observers: Array[Observer],32  observersCleared: Bool,33  redacted: Array[Str],34  redactWhen: Option[fn(Str) -> Bool]35}3637/// The headers whose values observers never see unless the program says otherwise.38const SENSITIVE: Array[Str] = ["authorization", "proxy-authorization", "cookie", "set-cookie"]3940/// The configuration of the client with the given name.41export fn named(name: Str) -> Builder {42  Builder {43    name: name,44    everyClient: false,45    clientActions: [],46    handlers: [],47    handlerEdits: [],48    primary: None,49    transportEdits: [],50    lifetime: None,51    observers: [],52    observersCleared: false,53    redacted: [],54    redactWhen: None55  }56}5758/// The configuration applied to every client before its own.59export fn defaults() -> Builder { Builder{..named(Defaults.DEFAULT_NAME), everyClient: true} }6061/// One name's configuration followed by another's: settings that add are joined in order, and62/// settings that replace are taken from the later one when it has them.63export fn combine(first: &Builder, second: &Builder) -> Builder {64  Builder {65    name: second.name,66    everyClient: first.everyClient && second.everyClient,67    clientActions: first.clientActions.concat(second.clientActions),68    handlers: first.handlers.concat(second.handlers),69    handlerEdits: first.handlerEdits.concat(second.handlerEdits),70    primary: if Option.isSome(&second.primary) { second.primary } else { first.primary },71    transportEdits: first.transportEdits.concat(second.transportEdits),72    lifetime: if Option.isSome(&second.lifetime) { second.lifetime } else { first.lifetime },73    observers: if second.observersCleared { second.observers } else { first.observers.concat(second.observers) },74    observersCleared: first.observersCleared || second.observersCleared,75    redacted: first.redacted.concat(second.redacted),76    redactWhen: if Option.isSome(&second.redactWhen) { second.redactWhen } else { first.redactWhen }77  }78}7980/// How long the builder's handler pipeline is reused before the next one is built.81export fn lifetimeOf(builder: &Builder) -> Int { Option.unwrapOr(builder.lifetime, Defaults.HANDLER_LIFETIME) }8283/// Whether observers see a header's value: never for the sensitive headers and the named ones, and84/// never when the predicate holds.85export fn redaction(builder: &Builder) -> fn(Str) -> Bool {86  let names = SENSITIVE.concat(builder.redacted).map(|name: Str| name.toLower())87  let predicate = builder.redactWhen88  fn(header: Str) -> Bool {89    let lower = header.toLower()90    names.contains(lower) || (match predicate {91        case Some(test) => test(lower)92        case None => false93      })94  }95}9697/** @Factory.Builder.Configuring — a named client configured one decision at a time */98export trait Configuring {99  /// Resolves relative addresses against a base address.100  fn withBaseAddress(self: &Self, address: Str) -> Self101  /// Adds a header to every request that does not carry it.102  fn withDefaultHeader(self: &Self, name: Str, value: Str) -> Self103  /// Gives each request this many milliseconds, or `Defaults.INFINITE`.104  fn withTimeout(self: &Self, millis: Int) -> Self105  /// Buffers at most this many response body bytes.106  fn withMaxResponseContentBufferSize(self: &Self, bytes: Int) -> Self107  /// Sends the requests the client's helpers build with this version and policy.108  fn withDefaultVersion(self: &Self, version: Http.Version, policy: Version.Policy) -> Self109  /// Changes the client in any other way, after the settings before it.110  fn configureClient(self: &Self, action: fn(Client.Client) -> Client.Client) -> Self111  /// Adds a handler inside the ones added before it.112  fn withHandler(self: &Self, handler: Handler.Handler) -> Self113  /// Adds a handler made afresh for every handler pipeline the factory builds.114  fn withHandlerFactory(self: &Self, make: fn() -> Handler.Handler) -> Self115  /// Rearranges the handlers, once every one has been added.116  fn configureHandlers(self: &Self, edit: fn(Array[Handler.Handler]) -> Array[Handler.Handler]) -> Self117  /// Uses a pooled transport with these options as the primary handler.118  fn withTransport(self: &Self, options: Transport.Options) -> Self119  /// Changes the options of the pooled transport, after the settings before it.120  fn configureTransport(self: &Self, edit: fn(Transport.Options) -> Transport.Options) -> Self121  /// Uses a primary handler of the program's own, made afresh for every handler pipeline.122  fn withPrimary(self: &Self, make: fn() -> Handler.Send) -> Self123  /// Reuses each handler pipeline for this many milliseconds, or `Defaults.INFINITE`.124  fn withHandlerLifetime(self: &Self, millis: Int) -> Self125  /// Adds an observer around and inside the handlers.126  fn observedBy(self: &Self, observer: Observer) -> Self127  /// Removes every observer added before, including the defaults'.128  fn withoutObservers(self: &Self) -> Self129  /// Hides the values of the named headers from observers.130  fn redactingHeaders(self: &Self, names: &Array[Str]) -> Self131  /// Hides the value of every header the predicate holds for from observers.132  fn redactingWhen(self: &Self, test: fn(Str) -> Bool) -> Self133}134135impl Configuring for Builder {136  /// Resolves relative addresses against a base address.137  fn withBaseAddress(self: &Self, address: Str) -> Self { self.configureClient(|client: Client.Client| client.withBaseAddress(address)) }138139  /// Adds a header to every request that does not carry it.140  fn withDefaultHeader(self: &Self, name: Str, value: Str) -> Self { self.configureClient(|client: Client.Client| client.withDefaultHeader(name, value)) }141142  /// Gives each request this many milliseconds, or `Defaults.INFINITE`.143  fn withTimeout(self: &Self, millis: Int) -> Self { self.configureClient(|client: Client.Client| client.withTimeout(millis)) }144145  /// Buffers at most this many response body bytes.146  fn withMaxResponseContentBufferSize(self: &Self, bytes: Int) -> Self { self.configureClient(|client: Client.Client| client.withMaxResponseContentBufferSize(bytes)) }147148  /// Sends the requests the client's helpers build with this version and policy.149  fn withDefaultVersion(self: &Self, version: Http.Version, policy: Version.Policy) -> Self {150    self.configureClient(|client: Client.Client| client.withDefaultVersion(version, policy))151  }152153  /// Changes the client in any other way, after the settings before it.154  fn configureClient(self: &Self, action: fn(Client.Client) -> Client.Client) -> Self { Builder{..*self, clientActions: self.clientActions.push(action)} }155156  /// Adds a handler inside the ones added before it.157  fn withHandler(self: &Self, handler: Handler.Handler) -> Self { self.withHandlerFactory(|| handler) }158159  /// Adds a handler made afresh for every handler pipeline the factory builds.160  fn withHandlerFactory(self: &Self, make: fn() -> Handler.Handler) -> Self { Builder{..*self, handlers: self.handlers.push(make)} }161162  /// Rearranges the handlers, once every one has been added.163  fn configureHandlers(self: &Self, edit: fn(Array[Handler.Handler]) -> Array[Handler.Handler]) -> Self { Builder{..*self, handlerEdits: self.handlerEdits.push(edit)} }164165  /// Uses a pooled transport with these options as the primary handler.166  fn withTransport(self: &Self, options: Transport.Options) -> Self { Builder{..*self, primary: Some(Pooled(options))} }167168  /// Changes the options of the pooled transport, after the settings before it.169  fn configureTransport(self: &Self, edit: fn(Transport.Options) -> Transport.Options) -> Self { Builder{..*self, transportEdits: self.transportEdits.push(edit)} }170171  /// Uses a primary handler of the program's own, made afresh for every handler pipeline.172  fn withPrimary(self: &Self, make: fn() -> Handler.Send) -> Self { Builder{..*self, primary: Some(Custom(make))} }173174  /// Reuses each handler pipeline for this many milliseconds, or `Defaults.INFINITE`.175  fn withHandlerLifetime(self: &Self, millis: Int) -> Self { Builder{..*self, lifetime: Some(millis)} }176177  /// Adds an observer around and inside the handlers.178  fn observedBy(self: &Self, observer: Observer) -> Self { Builder{..*self, observers: self.observers.push(observer)} }179180  /// Removes every observer added before, including the defaults'.181  fn withoutObservers(self: &Self) -> Self { Builder{..*self, observers: [], observersCleared: true} }182183  /// Hides the values of the named headers from observers.184  fn redactingHeaders(self: &Self, names: &Array[Str]) -> Self { Builder{..*self, redacted: self.redacted.concat(*names)} }185186  /// Hides the value of every header the predicate holds for from observers.187  fn redactingWhen(self: &Self, test: fn(Str) -> Bool) -> Self { Builder{..*self, redactWhen: Some(test)} }188}189