Repository navigation
Processors And Exceptions
Every request kind runs through the same order, outermost first; a layer with no components is left out:
- Exception actions and exception handlers.
- Pre-processors, in registration order.
- Post-processors, in registration order, after the handler answered a value.
- Behaviors, open and per kind, in registration order.
- 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.puduOutput:
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.