///|
/// Application event categories emitted by the protocol services.
pub(all) enum ServiceEventKind {
FrameReceivedEvent
FrameSentEvent
AlarmRaisedEvent
AlarmClearedEvent
CommandUpdatedEvent
DeviceHealthEvent
MaintenanceDueEvent
GatewayDeniedEvent
} derive(Debug, Eq)
///|
/// Delivery state used by durable event consumers.
pub(all) enum EventDelivery {
EventPending
EventDelivered
EventFailed
EventDeadLetter
} derive(Debug, Eq)
///|
/// Small immutable event envelope suitable for an in-memory or file queue.
pub(all) struct ServiceEvent {
id : String
kind : ServiceEventKind
source : String
timestamp : Int
payload : Array[Byte]
mut delivery : EventDelivery
mut attempts : Int
}
///|
pub fn ServiceEvent::new(
id : String,
kind : ServiceEventKind,
source : String,
timestamp : Int,
payload : Array[Byte],
) -> Result[ServiceEvent, ModbusError] {
if id == "" || source == "" || timestamp < 0 || payload.length() > 65535 {
return Err(InvalidData)
}
Ok({
id,
kind,
source,
timestamp,
payload: payload.copy(),
delivery: EventPending,
attempts: 0,
})
}
///|
pub fn ServiceEvent::id(self : ServiceEvent) -> String {
self.id
}
///|
pub fn ServiceEvent::kind(self : ServiceEvent) -> ServiceEventKind {
self.kind
}
///|
pub fn ServiceEvent::source(self : ServiceEvent) -> String {
self.source
}
///|
pub fn ServiceEvent::timestamp(self : ServiceEvent) -> Int {
self.timestamp
}
///|
pub fn ServiceEvent::payload(self : ServiceEvent) -> Array[Byte] {
self.payload.copy()
}
///|
pub fn ServiceEvent::delivery(self : ServiceEvent) -> EventDelivery {
self.delivery
}
///|
pub fn ServiceEvent::attempts(self : ServiceEvent) -> Int {
self.attempts
}
///|
pub fn ServiceEvent::mark_delivered(self : ServiceEvent) -> Unit {
self.delivery = EventDelivered
}
///|
pub fn ServiceEvent::mark_failed(
self : ServiceEvent,
dead_letter_after : Int,
) -> Result[Unit, ModbusError] {
if dead_letter_after < 1 {
return Err(InvalidData)
}
self.attempts += 1
self.delivery = if self.attempts >= dead_letter_after {
EventDeadLetter
} else {
EventFailed
}
Ok(())
}
///|
pub fn service_event_kind_name(kind : ServiceEventKind) -> String {
match kind {
FrameReceivedEvent => "frame-received"
FrameSentEvent => "frame-sent"
AlarmRaisedEvent => "alarm-raised"
AlarmClearedEvent => "alarm-cleared"
CommandUpdatedEvent => "command-updated"
DeviceHealthEvent => "device-health"
MaintenanceDueEvent => "maintenance-due"
GatewayDeniedEvent => "gateway-denied"
}
}
///|
/// Subscription filter for one consumer.
pub(all) struct EventSubscription {
id : String
source : String?
kind : ServiceEventKind?
mut enabled : Bool
}
///|
pub fn EventSubscription::new(
id : String,
source? : String = "",
kind? : ServiceEventKind = FrameReceivedEvent,
) -> Result[EventSubscription, ModbusError] {
if id == "" {
return Err(InvalidData)
}
let selected_source = if source == "" { None } else { Some(source) }
let selected_kind = if source == "" { None } else { Some(kind) }
Ok({ id, source: selected_source, kind: selected_kind, enabled: true })
}
///|
pub fn EventSubscription::id(self : EventSubscription) -> String {
self.id
}
///|
pub fn EventSubscription::enabled(self : EventSubscription) -> Bool {
self.enabled
}
///|
pub fn EventSubscription::set_enabled(
self : EventSubscription,
enabled : Bool,
) -> Unit {
self.enabled = enabled
}
///|
pub fn EventSubscription::matches(
self : EventSubscription,
event : ServiceEvent,
) -> Bool {
if !self.enabled {
return false
}
let source_ok = match self.source {
Some(source) => source == event.source()
None => true
}
let kind_ok = match self.kind {
Some(kind) => kind == event.kind()
None => true
}
source_ok && kind_ok
}
///|
/// Event queue with bounded pending and dead-letter retention.
pub(all) struct ServiceEventBus {
queue : Array[ServiceEvent]
dead_letters : Array[ServiceEvent]
subscriptions : Array[EventSubscription]
max_queue : Int
max_dead_letters : Int
max_subscriptions : Int
}
///|
pub fn ServiceEventBus::new(
max_queue? : Int = 8192,
max_dead_letters? : Int = 1024,
max_subscriptions? : Int = 256,
) -> Result[ServiceEventBus, ModbusError] {
if max_queue < 1 || max_dead_letters < 1 || max_subscriptions < 1 {
Err(InvalidData)
} else {
Ok({
queue: [],
dead_letters: [],
subscriptions: [],
max_queue,
max_dead_letters,
max_subscriptions,
})
}
}
///|
pub fn ServiceEventBus::queue_count(self : ServiceEventBus) -> Int {
self.queue.length()
}
///|
pub fn ServiceEventBus::dead_letter_count(self : ServiceEventBus) -> Int {
self.dead_letters.length()
}
///|
pub fn ServiceEventBus::subscription_count(self : ServiceEventBus) -> Int {
self.subscriptions.length()
}
///|
pub fn ServiceEventBus::subscribe(
self : ServiceEventBus,
subscription : EventSubscription,
) -> Result[Unit, ModbusError] {
if self.subscriptions.length() >= self.max_subscriptions {
return Err(CapacityExceeded)
}
for existing in self.subscriptions {
if existing.id() == subscription.id() {
return Err(InvalidData)
}
}
self.subscriptions.push(subscription)
Ok(())
}
///|
pub fn ServiceEventBus::unsubscribe(
self : ServiceEventBus,
id : String,
) -> Result[EventSubscription, ModbusError] {
for i in 0.. Result[Unit, ModbusError] {
for existing in self.queue {
if existing.id() == event.id() {
return Err(InvalidData)
}
}
if self.queue.length() >= self.max_queue {
return Err(CapacityExceeded)
}
self.queue.push(event)
Ok(())
}
///|
/// Claim the next event matching a subscription.
pub fn ServiceEventBus::claim(
self : ServiceEventBus,
subscription_id : String,
) -> Result[ServiceEvent, ModbusError] {
let subscription = match self.find_subscription(subscription_id) {
Some(value) => value
None => return Err(InvalidData)
}
for i in 0.. EventSubscription? {
for subscription in self.subscriptions {
if subscription.id() == id {
return Some(subscription)
}
}
None
}
///|
/// Acknowledge an event or return it to the queue after a failed attempt.
pub fn ServiceEventBus::ack(
self : ServiceEventBus,
event : ServiceEvent,
success : Bool,
dead_letter_after? : Int = 3,
) -> Result[Unit, ModbusError] {
if success {
event.mark_delivered()
Ok(())
} else {
match event.mark_failed(dead_letter_after) {
Err(error) => return Err(error)
Ok(_) => ()
}
match event.delivery() {
EventDeadLetter => {
if self.dead_letters.length() >= self.max_dead_letters {
ignore(self.dead_letters.remove(0))
}
self.dead_letters.push(event)
Ok(())
}
_ => self.publish(event)
}
}
}
///|
/// Return events filtered by kind without claiming them.
pub fn ServiceEventBus::peek_kind(
self : ServiceEventBus,
kind : ServiceEventKind,
) -> Array[ServiceEvent] {
let result : Array[ServiceEvent] = []
for event in self.queue {
if event.kind() == kind {
result.push(event)
}
}
result
}
///|
pub fn ServiceEventBus::clear_dead_letters(self : ServiceEventBus) -> Unit {
self.dead_letters.clear()
}