
Builder.pudu
Pudu189 lines9.8 KB
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}363738const SENSITIVE: Array[Str] = ["authorization", "proxy-authorization", "cookie", "set-cookie"]394041export 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}575859export fn defaults() -> Builder { Builder{..named(Defaults.DEFAULT_NAME), everyClient: true} }60616263export 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}798081export fn lifetimeOf(builder: &Builder) -> Int { Option.unwrapOr(builder.lifetime, Defaults.HANDLER_LIFETIME) }82838485export 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 100 fn withBaseAddress(self: &Self, address: Str) -> Self101 102 fn withDefaultHeader(self: &Self, name: Str, value: Str) -> Self103 104 fn withTimeout(self: &Self, millis: Int) -> Self105 106 fn withMaxResponseContentBufferSize(self: &Self, bytes: Int) -> Self107 108 fn withDefaultVersion(self: &Self, version: Http.Version, policy: Version.Policy) -> Self109 110 fn configureClient(self: &Self, action: fn(Client.Client) -> Client.Client) -> Self111 112 fn withHandler(self: &Self, handler: Handler.Handler) -> Self113 114 fn withHandlerFactory(self: &Self, make: fn() -> Handler.Handler) -> Self115 116 fn configureHandlers(self: &Self, edit: fn(Array[Handler.Handler]) -> Array[Handler.Handler]) -> Self117 118 fn withTransport(self: &Self, options: Transport.Options) -> Self119 120 fn configureTransport(self: &Self, edit: fn(Transport.Options) -> Transport.Options) -> Self121 122 fn withPrimary(self: &Self, make: fn() -> Handler.Send) -> Self123 124 fn withHandlerLifetime(self: &Self, millis: Int) -> Self125 126 fn observedBy(self: &Self, observer: Observer) -> Self127 128 fn withoutObservers(self: &Self) -> Self129 130 fn redactingHeaders(self: &Self, names: &Array[Str]) -> Self131 132 fn redactingWhen(self: &Self, test: fn(Str) -> Bool) -> Self133}134135impl Configuring for Builder {136 137 fn withBaseAddress(self: &Self, address: Str) -> Self { self.configureClient(|client: Client.Client| client.withBaseAddress(address)) }138139 140 fn withDefaultHeader(self: &Self, name: Str, value: Str) -> Self { self.configureClient(|client: Client.Client| client.withDefaultHeader(name, value)) }141142 143 fn withTimeout(self: &Self, millis: Int) -> Self { self.configureClient(|client: Client.Client| client.withTimeout(millis)) }144145 146 fn withMaxResponseContentBufferSize(self: &Self, bytes: Int) -> Self { self.configureClient(|client: Client.Client| client.withMaxResponseContentBufferSize(bytes)) }147148 149 fn withDefaultVersion(self: &Self, version: Http.Version, policy: Version.Policy) -> Self {150 self.configureClient(|client: Client.Client| client.withDefaultVersion(version, policy))151 }152153 154 fn configureClient(self: &Self, action: fn(Client.Client) -> Client.Client) -> Self { Builder{..*self, clientActions: self.clientActions.push(action)} }155156 157 fn withHandler(self: &Self, handler: Handler.Handler) -> Self { self.withHandlerFactory(|| handler) }158159 160 fn withHandlerFactory(self: &Self, make: fn() -> Handler.Handler) -> Self { Builder{..*self, handlers: self.handlers.push(make)} }161162 163 fn configureHandlers(self: &Self, edit: fn(Array[Handler.Handler]) -> Array[Handler.Handler]) -> Self { Builder{..*self, handlerEdits: self.handlerEdits.push(edit)} }164165 166 fn withTransport(self: &Self, options: Transport.Options) -> Self { Builder{..*self, primary: Some(Pooled(options))} }167168 169 fn configureTransport(self: &Self, edit: fn(Transport.Options) -> Transport.Options) -> Self { Builder{..*self, transportEdits: self.transportEdits.push(edit)} }170171 172 fn withPrimary(self: &Self, make: fn() -> Handler.Send) -> Self { Builder{..*self, primary: Some(Custom(make))} }173174 175 fn withHandlerLifetime(self: &Self, millis: Int) -> Self { Builder{..*self, lifetime: Some(millis)} }176177 178 fn observedBy(self: &Self, observer: Observer) -> Self { Builder{..*self, observers: self.observers.push(observer)} }179180 181 fn withoutObservers(self: &Self) -> Self { Builder{..*self, observers: [], observersCleared: true} }182183 184 fn redactingHeaders(self: &Self, names: &Array[Str]) -> Self { Builder{..*self, redacted: self.redacted.concat(*names)} }185186 187 fn redactingWhen(self: &Self, test: fn(Str) -> Bool) -> Self { Builder{..*self, redactWhen: Some(test)} }188}189