///|
/// Transformation performed by a routing rule.
pub(all) enum RouteAction {
RouteForward(String)
RouteFanOut(Array[String])
RouteRename(String)
RouteAddTag(String)
RouteRaisePriority(Int)
RouteDrop(String)
} derive(Eq, Debug)
///|
pub(all) struct RouteRule {
name : String
topic_pattern : String
required_tag : String
min_priority : Int
rule_priority : Int
terminal : Bool
active : Bool
action : RouteAction
} derive(Eq, Debug)
///|
pub(all) struct RouteNote {
rule : String
input_topic : String
output_count : Int
message : String
} derive(Eq, Debug)
///|
pub(all) struct RoutingResult {
input : Event
events : Array[Event]
matched_rules : Int
dropped : Bool
notes : Array[RouteNote]
} derive(Eq, Debug)
///|
pub(all) struct EventRouter {
rules : Array[RouteRule]
} derive(Eq, Debug)
///|
pub fn RouteRule::new(
name : String,
topic_pattern : String,
action : RouteAction,
required_tag? : String = "",
min_priority? : Int = 0,
rule_priority? : Int = 0,
terminal? : Bool = false,
) -> RouteRule {
{
name,
topic_pattern,
required_tag,
min_priority,
rule_priority,
terminal,
active: true,
action,
}
}
///|
pub fn RouteRule::matches(self : RouteRule, event : Event) -> Bool {
self.active &&
topic_matches(self.topic_pattern, event.topic) &&
(self.required_tag == "" || event.has_tag(self.required_tag)) &&
event.priority >= self.min_priority
}
///|
pub fn RouteRule::is_valid(self : RouteRule) -> Bool {
validate_topic_pattern(self.topic_pattern) == PatternValid
}
///|
pub fn EventRouter::new() -> EventRouter {
{ rules: [] }
}
///|
fn insert_rule_by_priority(
rules : Array[RouteRule],
rule : RouteRule,
) -> Array[RouteRule] {
let result : Array[RouteRule] = []
let mut inserted = false
for existing in rules {
if !inserted && rule.rule_priority > existing.rule_priority {
result.push(rule)
inserted = true
}
result.push(existing)
}
if !inserted {
result.push(rule)
}
result
}
///|
pub fn EventRouter::add_rule(
self : EventRouter,
rule : RouteRule,
) -> EventRouter {
{ rules: insert_rule_by_priority(self.rules, rule) }
}
///|
pub fn EventRouter::rule_count(self : EventRouter) -> Int {
self.rules.length()
}
///|
pub fn EventRouter::active_rule_count(self : EventRouter) -> Int {
let mut count = 0
for rule in self.rules {
if rule.active {
count = count + 1
}
}
count
}
///|
pub fn EventRouter::disable(self : EventRouter, name : String) -> EventRouter {
let rules : Array[RouteRule] = []
for rule in self.rules {
if rule.name == name {
rules.push({ ..rule, active: false })
} else {
rules.push(rule)
}
}
{ rules, }
}
///|
pub fn EventRouter::enable(self : EventRouter, name : String) -> EventRouter {
let rules : Array[RouteRule] = []
for rule in self.rules {
if rule.name == name {
rules.push({ ..rule, active: true })
} else {
rules.push(rule)
}
}
{ rules, }
}
///|
pub fn EventRouter::remove_rule(
self : EventRouter,
name : String,
) -> EventRouter {
let rules : Array[RouteRule] = []
for rule in self.rules {
if rule.name != name {
rules.push(rule)
}
}
{ rules, }
}
///|
fn event_with_topic(event : Event, topic : String) -> Event {
{ ..event, topic, }
}
///|
fn apply_route_action(event : Event, action : RouteAction) -> Array[Event] {
let output : Array[Event] = []
match action {
RouteForward(topic) => output.push(event_with_topic(event, topic))
RouteFanOut(topics) =>
for topic in topics {
output.push(event_with_topic(event, topic))
}
RouteRename(name) => output.push({ ..event, name, })
RouteAddTag(tag) =>
output.push({ ..event, tags: append_tag(event.tags, tag) })
RouteRaisePriority(priority) =>
if event.priority < priority {
output.push({ ..event, priority, })
} else {
output.push(event)
}
RouteDrop(_) => ()
}
output
}
///|
fn route_action_message(action : RouteAction) -> String {
match action {
RouteForward(topic) => "forward:" + topic
RouteFanOut(topics) => "fan-out:" + topics.length().to_string()
RouteRename(name) => "rename:" + name
RouteAddTag(tag) => "tag:" + tag
RouteRaisePriority(priority) => "priority:" + priority.to_string()
RouteDrop(reason) => "drop:" + reason
}
}
///|
fn apply_rule_to_events(
rule : RouteRule,
events : Array[Event],
) -> (Array[Event], Int, Array[RouteNote]) {
let output : Array[Event] = []
let notes : Array[RouteNote] = []
let mut matches = 0
for event in events {
if rule.matches(event) {
matches = matches + 1
let transformed = apply_route_action(event, rule.action)
notes.push({
rule: rule.name,
input_topic: event.topic,
output_count: transformed.length(),
message: route_action_message(rule.action),
})
for next in transformed {
output.push(next)
}
} else {
output.push(event)
}
}
(output, matches, notes)
}
///|
pub fn EventRouter::route(self : EventRouter, event : Event) -> RoutingResult {
let mut current = [event]
let notes : Array[RouteNote] = []
let mut matched_rules = 0
let mut terminated = false
for rule in self.rules {
if !terminated && current.length() > 0 {
let (next, matches, rule_notes) = apply_rule_to_events(rule, current)
current = next
if matches > 0 {
matched_rules = matched_rules + 1
if rule.terminal {
terminated = true
}
}
for note in rule_notes {
notes.push(note)
}
}
}
{
input: event,
events: current,
matched_rules,
dropped: current.length() == 0,
notes,
}
}
///|
pub fn EventRouter::route_many(
self : EventRouter,
events : Array[Event],
) -> Array[RoutingResult] {
let results : Array[RoutingResult] = []
for event in events {
results.push(self.route(event))
}
results
}
///|
pub fn RoutingResult::output_count(self : RoutingResult) -> Int {
self.events.length()
}
///|
pub fn RoutingResult::first(self : RoutingResult) -> Event? {
self.events.get(0)
}
///|
pub fn RoutingResult::publish(
self : RoutingResult,
bus : EventBus,
) -> (EventBus, Array[DispatchReport]) {
bus.publish_many(self.events)
}
///|
pub fn EventRouter::invalid_rules(self : EventRouter) -> Array[RouteRule] {
let invalid : Array[RouteRule] = []
for rule in self.rules {
if !rule.is_valid() {
invalid.push(rule)
}
}
invalid
}