///|
/// Matching mode for a route condition.
pub(all) enum MatchMode {
Any
Exact(String)
} derive(Eq, Debug)
///|
/// A declarative WebHook route.
pub(all) struct HookRoute {
name : String
provider : MatchMode
event_type : MatchMode
enabled : Bool
} derive(Eq, Debug)
///|
/// A router is intentionally declarative so tests and CLIs can inspect it.
pub(all) struct HookRouter {
routes : Array[HookRoute]
fallback : HookResult
} derive(Eq, Debug)
///|
/// Build an exact matcher.
pub fn exact(value : StringView) -> MatchMode {
Exact(value.to_owned())
}
///|
/// Build a route for all providers and all event types.
pub fn HookRoute::HookRoute(
name : StringView,
provider? : MatchMode = Any,
event_type? : MatchMode = Any,
enabled? : Bool = true,
) -> HookRoute {
{ name: name.to_owned(), provider, event_type, enabled }
}
///|
/// Create an empty router.
pub fn new_hook_router() -> HookRouter {
{ routes: [], fallback: ignored(message="no route matched") }
}
///|
/// Replace the router fallback result.
pub fn HookRouter::with_fallback(
self : HookRouter,
fallback : HookResult,
) -> HookRouter {
{ routes: self.routes, fallback }
}
///|
/// Register a route and return the updated router.
pub fn HookRouter::add(self : HookRouter, route : HookRoute) -> HookRouter {
let routes = self.routes
routes.push(route)
{ routes, fallback: self.fallback }
}
///|
/// Register an exact provider/event route.
pub fn HookRouter::on(
self : HookRouter,
provider : HookProvider,
event_type : StringView,
name? : StringView = event_type,
) -> HookRouter {
self.add(
HookRoute(
name,
provider=exact(provider.slug()),
event_type=exact(event_type),
),
)
}
///|
/// Register a provider-only route.
pub fn HookRouter::on_provider(
self : HookRouter,
provider : HookProvider,
name? : StringView = "provider-route",
) -> HookRouter {
self.add(HookRoute(name, provider=exact(provider.slug())))
}
///|
/// Register an event-type-only route.
pub fn HookRouter::on_event(
self : HookRouter,
event_type : StringView,
name? : StringView = event_type,
) -> HookRouter {
self.add(HookRoute(name, event_type=exact(event_type)))
}
///|
/// Resolve the first matching route.
pub fn HookRouter::match_event(
self : HookRouter,
event : HookEvent,
) -> HookRoute? {
for route in self.routes {
if route.matches(event) {
return Some(route)
}
}
None
}
///|
/// Dispatch returns an accepted result for a matched route and fallback otherwise.
pub fn HookRouter::dispatch(self : HookRouter, event : HookEvent) -> HookResult {
match self.match_event(event) {
Some(route) => accepted(message="matched route " + route.name)
None => self.fallback
}
}
///|
/// Check whether a route accepts an event.
pub fn HookRoute::matches(self : HookRoute, event : HookEvent) -> Bool {
self.enabled &&
match_provider(self.provider, event.provider) &&
match_string(self.event_type, event.event_type)
}
///|
fn match_provider(mode : MatchMode, provider : HookProvider) -> Bool {
match mode {
Any => true
Exact(value) => value == provider.slug()
}
}
///|
fn match_string(mode : MatchMode, value : String) -> Bool {
match mode {
Any => true
Exact(expected) => expected == value
}
}
///|
/// Count enabled routes for diagnostics.
pub fn HookRouter::enabled_count(self : HookRouter) -> Int {
let mut count = 0
for route in self.routes {
if route.enabled {
count = count + 1
}
}
count
}