Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-resilience

Resilience pipelines for Pudu: retry, circuit breaker, timeout, fallback, hedging, rate limiting, and chaos injection

0.1.0Apache-2.01

InstallClose

Context.pudu

Pudu120 lines5.0 KB

GitHub ↗
1/** @Resilience.Context.Carrier — per-execution key, token, and properties */2module PuduLangResilience.Context34import Std.Concurrent.Cancel as Cancel5import Std.Decimal as Decimal6import Std.Map as Map7import Std.Option as Option8import PuduLangResilience as Resilience9import PuduLangResilience.Utils.Shared as Shared1011/** @Resilience.Context.Context — what one execution carries through a pipeline */12export type Context = {13  operationKey: Option[Str],14  token: Cancel.Token,15  properties: Shared.Shared[Map[Str, Str]]16}1718/** @Resilience.Context.Key — a typed name for one property */19export type Key[T] = { name: Str, encode: fn(T) -> Str, decode: fn(Str) -> Option[T] }2021/// A context with no operation key, a token that fires only when cancelled, and no properties.22export fn create() -> Context {23  Context{operationKey: None, token: Cancel.token(), properties: Shared.shared(Map.empty())}24}2526/// A fresh context naming the operation it runs.27export fn keyed(operationKey: Str) -> Context { Context{..create(), operationKey: Some(operationKey)} }2829/// A fresh context whose execution stops when `token` fires.30export fn cancellable(token: Cancel.Token) -> Context { Context{..create(), token: token} }3132/// The same context observing another token; properties stay shared with the original.33export fn withToken(source: &Context, token: Cancel.Token) -> Context { Context{..*source, token: token} }3435/// A copy of the context observing another token, with a separate copy of its properties.36export fn fork(source: &Context, token: Cancel.Token) -> Context {37  Context{..*source, token: token, properties: Shared.shared(Shared.current(&source.properties))}38}3940/// Copies every property of `source` into `target`, replacing properties of the same name.41export fn adopt(target: &Context, source: &Context) -> () {42  let incoming = Shared.current(&source.properties)43  Shared.update(&target.properties, fn(held: Map[Str, Str]) -> Map[Str, Str] { Map.merge(&held, &incoming) })44}4546/// A key whose values are written with `encode` and read back with `decode`.47export fn key[T](name: Str, encode: fn(T) -> Str, decode: fn(Str) -> Option[T]) -> Key[T] {48  Key{name: name, encode: encode, decode: decode}49}5051/// A key holding text.52export fn textKey(name: Str) -> Key[Str] {53  key(name, fn(value: Str) -> Str { value }, fn(text: Str) -> Option[Str] { Some(text) })54}5556/// A key holding a whole number.57export fn intKey(name: Str) -> Key[Int] {58  key(name, fn(value: Int) -> Str { show(value) }, fn(text: Str) -> Option[Int] {59      Option.andThen(Decimal.parse(text), fn(exact: Decimal) -> Option[Int] { Decimal.toInt(exact) })60    })61}6263/// A key holding a truth value.64export fn boolKey(name: Str) -> Key[Bool] {65  key(name, fn(value: Bool) -> Str { if value { "true" } else { "false" } }, fn(text: Str) -> Option[Bool] {66      if text == "true" { Some(true) } else if text == "false" { Some(false) } else { None }67    })68}6970/// Stores a property, replacing any value under the same name.71export fn set[T](target: &Context, name: &Key[T], value: T) -> () {72  let text = (name.encode)(value)73  let field = name.name74  Shared.update(&target.properties, fn(held: Map[Str, Str]) -> Map[Str, Str] { Map.insert(&held, field, text) })75}7677/// The property under a key, when it is present and reads back as the key's type.78export fn get[T](source: &Context, name: &Key[T]) -> Option[T] {79  Option.andThen(Map.get(&Shared.current(&source.properties), name.name), name.decode)80}8182/// The property under a key, or `fallback` when it is absent or unreadable.83export fn getOr[T](source: &Context, name: &Key[T], fallback: T) -> T { Option.unwrapOr(get(source, name), fallback) }8485/// Whether a property is stored under the key's name.86export fn has[T](source: &Context, name: &Key[T]) -> Bool {87  Map.containsKey(&Shared.current(&source.properties), name.name)88}8990/// Removes the property under a key.91export fn remove[T](target: &Context, name: &Key[T]) -> () {92  let field = name.name93  Shared.update(&target.properties, fn(held: Map[Str, Str]) -> Map[Str, Str] { Map.remove(&held, field) })94}9596/// The names of every stored property, in order.97export fn names(source: &Context) -> Array[Str] { Map.keys(&Shared.current(&source.properties)) }9899/// Asks the execution holding this context to stop. The first reason given is kept.100export fn cancel(target: &Context, why: Str) -> () { let _cancelled = Cancel.cancel(&target.token, why) }101102/// Whether the context's token has fired.103export fn stopped(source: &Context) -> Bool { Cancel.stopped(&source.token) }104105/// `Ok` while the token has not fired, and `Cancelled` once it has, so a callback stops with `?`.106export fn check[E](source: &Context) -> Result[(), Resilience.Failure[E]] {107  match Cancel.check(&source.token) {108    case Ok(_) => Ok(())109    case Err(why) => Err(Resilience.Cancelled(Cancel.explain(&why)))110  }111}112113/// Sleeps `millis` on the system clock, or answers `Cancelled` as soon as the token fires.114export fn pause[E](source: &Context, millis: Int) -> Result[(), Resilience.Failure[E]] {115  match Cancel.pause(&source.token, millis) {116    case Ok(_) => Ok(())117    case Err(why) => Err(Resilience.Cancelled(Cancel.explain(&why)))118  }119}120