///|
/// Context supplied to every middleware and terminal handler.
pub(all) struct HookContext {
provider : HookProvider
delivery_id : String
middleware_index : Int
} derive(Eq, Debug)
///|
/// Middleware either forwards an enriched event or stops processing.
pub(all) enum MiddlewareOutcome {
Continue(HookEvent)
Halt(HookResult)
} derive(Debug)
///|
pub type MiddlewareFn = (HookEvent, HookContext) -> MiddlewareOutcome
///|
pub type HookHandler = (HookEvent, HookContext) -> HookResult
///|
/// Named middleware entry used by the processing pipeline.
pub struct HookMiddleware {
name : String
callback : MiddlewareFn
}
///|
/// Ordered middleware pipeline with a terminal event handler.
pub struct HookPipeline {
middlewares : Array[HookMiddleware]
handler : HookHandler
}
///|
/// Execution details exposed to HTTP adapters, logs, and tests.
pub(all) struct PipelineResult {
event : HookEvent
result : HookResult
executed : Array[String]
} derive(Debug)
///|
pub fn HookMiddleware::HookMiddleware(
name : StringView,
callback : MiddlewareFn,
) -> HookMiddleware {
{ name: name.to_owned(), callback }
}
///|
pub fn new_hook_pipeline(handler : HookHandler) -> HookPipeline {
{ middlewares: [], handler }
}
///|
/// Append middleware while preserving registration order.
pub fn HookPipeline::use_middleware(
self : HookPipeline,
name : StringView,
callback : MiddlewareFn,
) -> HookPipeline {
let middlewares : Array[HookMiddleware] = []
for middleware in self.middlewares {
middlewares.push(middleware)
}
middlewares.push(HookMiddleware(name, callback))
{ middlewares, handler: self.handler }
}
///|
pub fn HookPipeline::middleware_count(self : HookPipeline) -> Int {
self.middlewares.length()
}
///|
/// Execute middleware in order and invoke the handler only when none halts.
pub fn HookPipeline::run(
self : HookPipeline,
initial : HookEvent,
) -> PipelineResult {
let mut current = initial
let executed : Array[String] = []
let mut index = 0
for middleware in self.middlewares {
let context = {
provider: current.provider,
delivery_id: current.delivery_id,
middleware_index: index,
}
executed.push(middleware.name)
match (middleware.callback)(current, context) {
Continue(updated) => current = updated
Halt(result) => return { event: current, result, executed }
}
index = index + 1
}
let context = {
provider: current.provider,
delivery_id: current.delivery_id,
middleware_index: index,
}
{ event: current, result: (self.handler)(current, context), executed }
}
///|
/// Built-in middleware that requires valid JSON before routing.
pub fn require_json_middleware() -> MiddlewareFn {
(event, _) => {
if event.attribute("payload.valid_json") == Some("true") {
Continue(event)
} else {
Halt(failed(message="request body is not valid JSON"))
}
}
}
///|
/// Built-in middleware that enriches every event with a static attribute.
pub fn attribute_middleware(key : String, value : String) -> MiddlewareFn {
(event, _) => Continue(event.with_attribute(key, value))
}
///|
/// Built-in middleware that rejects events missing a parsed attribute.
pub fn require_attribute_middleware(key : String) -> MiddlewareFn {
(event, _) => {
if event.attribute(key) is Some(_) {
Continue(event)
} else {
Halt(failed(message="required event attribute is missing: " + key))
}
}
}