Skip to content

Processors And Exceptions

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

Pudu

Processors And Exceptions

Every request kind runs through the same order, outermost first; a layer with no components is left out:

  1. Exception actions and exception handlers.
  2. Pre-processors, in registration order.
  3. Post-processors, in registration order, after the handler answered a value.
  4. Behaviors, open and per kind, in registration order.
  5. The handler.

An exception handler may answer a value in place of a failure; the first one that does wins. An exception action observes a failure that keeps propagating. By default (Catalog.ForUnhandled) actions sit outside the handlers and see only failures nobody recovered; with Catalog.ForAll they sit inside and see every failure:

module Ex04Processors

import Std.Io as Io
import PuduLangMediator.Catalog as Catalog
import PuduLangMediator.Context as Context
import PuduLangMediator.Mediator as Mediator
import PuduLangMediator as Messaging
import PuduLangMediator.Registration as Registration
import PuduLangMediator.Request as Request

type Fault = Missing | Unavailable

fn say(line: Str) -> () { let _said = Io.writeLine(line) }

export fn main() -> Int {
  let profile: Request.Kind[Int, Str, Fault] = Request.kind("profiles.get")
  let registrations = [
    Registration.preProcessor(&profile, fn(id: Int, _context: Context.Context) -> Messaging.Outcome[(), Fault] {
        say("pre: loading " + show(id))
        if id < 0 { Messaging.refuse("ids are positive") } else { Ok(()) }
      }),
    Registration.postProcessor(&profile, fn(id: Int, name: Str, _context: Context.Context) -> Messaging.Outcome[(), Fault] {
        say("post: " + show(id) + " is " + name)
        Ok(())
      }),
    Registration.exceptionHandler(&profile, fn(_id: Int, failure: Messaging.Failure[Fault], _context: Context.Context) -> Option[Str] {
        if Messaging.raised(&failure) == Some(Missing) { Some("guest") } else { None }
      }),
    Registration.exceptionAction(&profile, fn(id: Int, failure: Messaging.Failure[Fault], _context: Context.Context) -> () {
        say("action: " + show(id) + " failed: " + Messaging.describe(&failure))
      }),
    Registration.handler(&profile, fn(id: Int, _context: Context.Context) -> Messaging.Outcome[Str, Fault] {
        match id {
          case 1 => Ok("ada")
          case 2 => Messaging.raise(Missing)
          case _ => Messaging.raise(Unavailable)
        }
      })
  ]
  let mediator = match Mediator.build(registrations) {
    case Ok(built) => built
    case Err(invalid) => panic(Mediator.explain(&invalid))
  }
  let ada = Mediator.send(&mediator, &profile, 1)
  let guest = Mediator.send(&mediator, &profile, 2)
  let down = Mediator.send(&mediator, &profile, 3)
  let negative = Mediator.send(&mediator, &profile, -1)
  say("-- every failure in scope --")
  let everyFailure = match Mediator.buildWith(&Mediator.Options{..Mediator.defaults(), actionScope: Catalog.ForAll}, registrations) {
    case Ok(built) => built
    case Err(invalid) => panic(Mediator.explain(&invalid))
  }
  let seen = Mediator.send(&everyFailure, &profile, 2)
  if ada == Ok("ada") && guest == Ok("guest") && down == Err(Messaging.Raised(Unavailable)) && negative == Err(Messaging.Refused("ids are positive")) && seen == Ok("guest") { 0 } else { 1 }
}

Check it and run it:

pudu check src/Ex04Processors.pudu
pudu run src/Ex04Processors.pudu

Output:

pre: loading 1
post: 1 is ada
pre: loading 2
pre: loading 3
action: 3 failed: The handler failed: Unavailable
pre: loading -1
action: -1 failed: The message was refused: ids are positive
-- every failure in scope --
pre: loading 2
action: 2 failed: The handler failed: Missing

The missing profile is recovered as guest and no action sees it; the unavailable one is not recovered, so the action reports it. A failing pre-processor stops the request before any behavior or the handler runs, and exception components still see that failure. With every failure in scope, the action sees the missing profile too, before it is recovered.

Open forms exist for pre-processors (Open.PreProcessor), post-processors (Open.PostProcessor), and exception actions (Open.ExceptionAction). Exception handlers are per kind only: recovering needs a value of the kind's response type.

Related

Clone this wiki locally