Skip to content

Integrations

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

Pudu

Integrations

Three behaviors build on other Pudu packages, installed with this one:

Module Package What it adds
Behaviors.Validation pudu-lang-validator register(&kind, validator): answers Invalid with every violation before the handler runs; registerStream for streams
Behaviors.Logging pudu-lang-log Logging.of(logger): one recorder that is an open behavior, open stream behavior, and open notification handler
Behaviors.Resilient pudu-lang-resilience register(&kind, pipeline): retries, timeouts, circuit breaking, rate limiting, hedging, and fallback around the rest of the pipeline
module Ex10Integrations

import Std.Io as Io
import PuduLangLog.Configuration as Configuration
import PuduLangLog as Log
import PuduLangLog.Sinks.Memory as Memory
import PuduLangMediator.Behaviors.Logging as Logging
import PuduLangMediator.Behaviors.Resilient as Resilient
import PuduLangMediator.Behaviors.Validation as Validation
import PuduLangMediator.Context as Context
import PuduLangMediator.Mediator as Mediator
import PuduLangMediator as Messaging
import PuduLangMediator.Registration as Registration
import PuduLangMediator.Request as Request
import PuduLangMediator.Utils.Shared as Shared
import PuduLangResilience.Pipeline as Pipeline
import PuduLangResilience.Retry as Retry
import PuduLangResilience.Timeout as Timeout
import PuduLangValidator.Rule as Rule
import PuduLangValidator.Rules.Number as NumberRule
import PuduLangValidator.Rules.Text as TextRule
import PuduLangValidator.Validator as Validator

type Transfer = { account: Str, amount: Int }

fn rules() -> Validator.Validator[Transfer] {
  let account = Rule.build(TextRule.notEmpty(Rule.ruleFor("account", |transfer: Transfer| transfer.account)))
  let amount = Rule.build(NumberRule.greaterThan(Rule.ruleFor("amount", |transfer: Transfer| transfer.amount), 0))
  Validator.add(Validator.add(Validator.create(), account), amount)
}

export fn main() -> Int {
  let transfer: Request.Kind[Transfer, Str, Str] = Request.kind("payments.transfer")
  let memory = Memory.create()
  let logger = Configuration.create().minimumLevel(Log.Information).writeTo(Memory.sink(&memory)).createLogger()
  let guarded = match Pipeline.build([
      Retry.strategy(Retry.Options{..Retry.defaults(), maxRetryAttempts: 2, delay: 10}),
      Timeout.strategy(Timeout.after(1000))
    ]) {
    case Ok(pipeline) => pipeline
    case Err(invalid) => panic(Pipeline.explain(&invalid))
  }
  let calls = Shared.shared(0)
  let mediator = match Mediator.build([
      Registration.openBehavior(Logging.of(logger)),
      Validation.register(&transfer, rules()),
      Resilient.register(&transfer, guarded),
      Registration.handler(&transfer, fn(request: Transfer, _context: Context.Context) -> Messaging.Outcome[Str, Str] {
          let made = Shared.change(&calls, fn(count: Int) -> (Int, Int) { (count + 1, count + 1) })
          if made == 1 { Messaging.raise("gateway busy") } else { Ok("sent " + show(request.amount) + " to " + request.account) }
        })
    ]) {
    case Ok(built) => built
    case Err(invalid) => panic(Mediator.explain(&invalid))
  }
  let sent = Mediator.send(&mediator, &transfer, Transfer{account: "ES91", amount: 25})
  let invalid = Mediator.send(&mediator, &transfer, Transfer{account: "", amount: 0})
  let _sent = Io.writeLine(Messaging.summarize(&sent) + " after " + show(Shared.current(&calls)) + " calls")
  let _invalid = Io.writeLine(Messaging.summarize(&invalid))
  for event in Memory.events(&memory) {
    let _said = Io.writeLine("log " + show(event.level) + ": " + event.template.text)
  }
  if sent == Ok("sent 25 to ES91") && Shared.current(&calls) == 2 { 0 } else { 1 }
}

Check it and run it:

pudu check src/Ex10Integrations.pudu
pudu run src/Ex10Integrations.pudu

Output:

Ok("sent 25 to ES91") after 2 calls
The message is invalid: account: Text is required.; amount: Number must be greater than 0.
log Information: Handled {MessageName} in {Elapsed} ms
log Warning: {MessageName} failed in {Elapsed} ms: {Failure}

The logging recorder is registered first, so it is the outermost behavior: it sees the transfer once, after the resilience behavior retried the busy gateway. The invalid transfer never reached the handler; Messaging.violations lists each property, message, and rule code. A strategy's rejection (a timeout, an open circuit, a rate limit) comes back as Refused with its description, and a caller's cancellation is never retried.

Related

Clone this wiki locally