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

FallbackTest.pudu

Pudu61 lines3.5 KB

GitHub ↗
1/** @Test.Fallback.Suite — substitutes for handled outcomes */2module PuduLangResilience.FallbackTest34import Std.Io as Io5import Std.Result as Result6import Std.Sync as Sync7import Std.Test as Test8import PuduLangResilience.Context as Context9import PuduLangResilience.Fallback as Fallback10import PuduLangResilience.Pipeline as Pipeline11import PuduLangResilience.Predicate as Predicate12import PuduLangResilience as Resilience1314/// A pipeline of one fallback strategy.15fn guarded(options: Fallback.Options[Int, Str]) -> Pipeline.Pipeline[Int, Str] {16  match Pipeline.build([Fallback.strategy(options)]) {17    case Ok(pipeline) => pipeline18    case Err(invalid) => panic(Pipeline.explain(&invalid))19  }20}2122/// A callback answering `outcome`.23fn answering(outcome: Resilience.Outcome[Int, Str]) -> fn(Context.Context) -> Resilience.Outcome[Int, Str] {24  fn(_context: Context.Context) -> Resilience.Outcome[Int, Str] { outcome }25}2627/// Runs the suite.28fn main() -> Int {29  let seen: Sync.Cell[Array[Str]] = Sync.cell([])30  let observed = Fallback.Options{..Fallback.withValue(0), onFallback: Some(fn(given: Predicate.Arguments[Int, Str]) -> () {31        let _kept = Sync.set(&seen, [Resilience.summarize(&given.outcome)])32      }) }33  let derived = Fallback.Options{..Fallback.defaults(), action: Some(fn(given: Predicate.Arguments[Int, Str]) -> Resilience.Outcome[Int, Str] {34        match given.outcome {35          case Err(Resilience.Raised(problem)) => Ok(problem.length())36          case _ => Ok(-1)37        }38      }) }39  let byResult = Fallback.Options{..Fallback.withValue(100), shouldHandle: Predicate.result(404)}40  let failing = Fallback.Options{..Fallback.defaults(), action: Some(fn(_given: Predicate.Arguments[Int, Str]) -> Resilience.Outcome[Int, Str] { Resilience.raise("no substitute") })}41  let missing = Pipeline.build([Fallback.strategy(Fallback.defaults())])42  let checks = Test.suite("Fallback", &[43      Test.equals("a handled failure is replaced by the substitute", &Pipeline.execute(&guarded(observed), answering(Resilience.raise("down"))), &Ok(0)),44      Test.equals("onFallback sees the handled outcome", &Sync.get(&seen), &Ok(["The operation failed: \"down\""])),45      Test.equals("a value passes through untouched", &Pipeline.execute(&guarded(observed), answering(Ok(5))), &Ok(5)),46      Test.equals("the substitute may depend on the outcome", &Pipeline.execute(&guarded(derived), answering(Resilience.raise("abc"))), &Ok(3)),47      Test.equals("a cancellation is not handled by default", &Pipeline.execute(&guarded(observed), answering(Err(Resilience.Cancelled("stop")))), &Err(Resilience.Cancelled("stop"))),48      Test.equals("a rejection by another strategy is handled by default", &Pipeline.execute(&guarded(observed), answering(Err(Resilience.TimedOut(10)))), &Ok(0)),49      Test.equals("a handled value is replaced", &Pipeline.execute(&guarded(byResult), answering(Ok(404))), &Ok(100)),50      Test.equals("an unhandled value is kept", &Pipeline.execute(&guarded(byResult), answering(Ok(200))), &Ok(200)),51      Test.equals("a failing substitute is answered as is", &Pipeline.execute(&guarded(failing), answering(Resilience.raise("down"))), &Resilience.raise("no substitute")),52      Test.equals("validation names the missing action", &Fallback.validate(&Fallback.defaults()), &["action is required"]),53      Test.equals("a fallback without an action is refused", &Result.err(missing), &Some(Pipeline.Invalid{problems: ["Fallback: action is required"]}))54    ])55  let ran = Test.run(&checks)56  for failure in Test.failuresOf(&ran) {57    let _reported = Io.writeErrorLine(failure)58  }59  Test.report(&ran)60}61