use crate::config::{kafka::KafkaTopologyConfig, redis::RedisTopologyConfig};
use crate::error::EventBusErrorType;
use crate::resources::{ConsumerMetrics, IncomingMessage, MessageMetadata};
use crate::plugins::event_bus::resources::{MessageQueue, ProvisionedTopology};
use async_trait::async_trait;
use bevy::prelude::{App, World};
use bevy_event_bus::BusMessage;
use crossbeam_channel::Receiver;
use std::any::Any;
use std::error::Error;
use std::fmt::{Debug, Display};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ManualCommitDescriptor {
pub backend: &'static str,
pub style: ManualCommitStyle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ManualCommitStyle {
OffsetQueue,
StreamAck,
}
pub trait ManualCommitHandle: Send + Sync {
fn register_resources(&self, world: &mut World);
fn descriptor(&self) -> ManualCommitDescriptor;
}
#[derive(Debug, Clone)]
pub struct LagReportingDescriptor {
pub backend: &'static str,
pub detail: &'static str,
}
pub trait LagReportingHandle: Send + Sync {
fn register_resources(&self, world: &mut World);
fn descriptor(&self) -> LagReportingDescriptor;
}
#[derive(Default)]
pub struct BackendPluginSetup {
pub ready_topics: Vec<String>,
pub message_stream: Option<Receiver<IncomingMessage>>,
pub manual_commit: Option<Box<dyn ManualCommitHandle>>,
pub lag_reporting: Option<Box<dyn LagReportingHandle>>,
pub kafka_topology: Option<KafkaTopologyConfig>,
pub redis_topology: Option<RedisTopologyConfig>,
}
#[derive(Debug, Clone, Default)]
pub struct BackendInstallSnapshot {
pub message_stream: bool,
pub manual_commit: Option<ManualCommitDescriptor>,
pub lag_reporting: Option<LagReportingDescriptor>,
}
impl BackendPluginSetup {
pub fn install(&mut self, world: &mut World) -> BackendInstallSnapshot {
let mut snapshot = BackendInstallSnapshot::default();
if let Some(topology) = self.kafka_topology.take() {
world
.resource_mut::<ProvisionedTopology>()
.record_kafka(topology);
}
if let Some(topology) = self.redis_topology.take() {
world
.resource_mut::<ProvisionedTopology>()
.record_redis(topology);
}
if let Some(receiver) = self.message_stream.take() {
world.insert_resource(MessageQueue { receiver });
snapshot.message_stream = true;
}
if let Some(handle) = self.manual_commit.take() {
snapshot.manual_commit = Some(handle.descriptor());
handle.register_resources(world);
}
if let Some(handle) = self.lag_reporting.take() {
snapshot.lag_reporting = Some(handle.descriptor());
handle.register_resources(world);
}
snapshot
}
}
#[derive(Debug, Clone)]
pub struct BackendConfigError {
backend: &'static str,
reason: String,
}
impl BackendConfigError {
pub fn new(backend: &'static str, reason: impl Into<String>) -> Self {
Self {
backend,
reason: reason.into(),
}
}
pub fn backend(&self) -> &'static str {
self.backend
}
pub fn reason(&self) -> &str {
&self.reason
}
}
impl Display for BackendConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Backend '{}' configuration error: {}",
self.backend, self.reason
)
}
}
impl Error for BackendConfigError {}
pub trait EventBusBackendConfig: Send + Sync + 'static {
fn as_any(&self) -> &dyn Any;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StreamTrimStrategy {
Exact,
Approximate,
}
#[derive(Clone, Debug)]
pub struct DeliveryFailure {
pub backend: &'static str,
pub kind: EventBusErrorType,
pub topic: String,
pub error: String,
pub metadata: Option<MessageMetadata>,
}
pub struct DeliveryFailureCallback {
inner: Arc<dyn Fn(DeliveryFailure) + Send + Sync>,
}
impl DeliveryFailureCallback {
pub fn new<F>(callback: F) -> Self
where
F: Fn(DeliveryFailure) + Send + Sync + 'static,
{
Self {
inner: Arc::new(callback),
}
}
pub fn call(&self, failure: DeliveryFailure) {
(self.inner)(failure);
}
}
impl Clone for DeliveryFailureCallback {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl Debug for DeliveryFailureCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DeliveryFailureCallback").finish()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct BackendSpecificSendOptions<'a> {
data: Option<&'a dyn Any>,
}
impl<'a> BackendSpecificSendOptions<'a> {
pub fn none() -> Self {
Self { data: None }
}
pub fn new(data: &'a dyn Any) -> Self {
Self { data: Some(data) }
}
pub fn as_any(&self) -> Option<&'a dyn Any> {
self.data
}
}
#[derive(Clone, Copy, Default)]
pub struct SendOptions<'a> {
pub partition_key: Option<&'a str>,
pub stream_trim: Option<(usize, StreamTrimStrategy)>,
pub backend: BackendSpecificSendOptions<'a>,
}
impl<'a> SendOptions<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn partition_key(mut self, key: &'a str) -> Self {
self.partition_key = Some(key);
self
}
pub fn stream_trim(mut self, maxlen: usize, strategy: StreamTrimStrategy) -> Self {
self.stream_trim = Some((maxlen, strategy));
self
}
pub fn backend_options(mut self, backend: BackendSpecificSendOptions<'a>) -> Self {
self.backend = backend;
self
}
}
#[derive(Clone, Copy, Default)]
pub struct ReceiveOptions<'a> {
pub consumer_group: Option<&'a str>,
}
impl<'a> ReceiveOptions<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn consumer_group(mut self, group_id: &'a str) -> Self {
self.consumer_group = Some(group_id);
self
}
}
#[async_trait]
pub trait EventBusBackend: Send + Sync + 'static + Debug {
fn clone_box(&self) -> Box<dyn EventBusBackend>;
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn backend_name(&self) -> &'static str {
"unknown"
}
fn configure_plugin(&self, _app: &mut App) {}
fn setup_plugin(&self, _world: &mut World) -> BackendPluginSetup {
BackendPluginSetup::default()
}
fn augment_metrics(&self, _metrics: &mut ConsumerMetrics) {}
fn configure(&mut self, _config: &dyn EventBusBackendConfig) -> Result<(), BackendConfigError> {
let _ = _config;
Ok(())
}
fn apply_event_bindings(&self, _app: &mut App) {}
async fn connect(&mut self) -> bool;
async fn disconnect(&mut self) -> bool;
fn try_send_serialized(
&self,
event_json: &[u8],
topic: &str,
options: SendOptions<'_>,
failure_handler: Option<Arc<DeliveryFailureCallback>>,
) -> bool;
async fn receive_serialized(&self, topic: &str, options: ReceiveOptions<'_>) -> Vec<Vec<u8>>;
async fn flush(&self) -> Result<(), String> {
Ok(()) }
}
#[async_trait]
pub trait ManualCommitController: EventBusBackend {
async fn enable_manual_commits(&mut self, group_id: &str) -> Result<(), String>;
async fn commit_offset(&self, topic: &str, partition: i32, offset: i64) -> Result<(), String>;
}
#[async_trait]
pub trait LagReportingBackend: EventBusBackend {
async fn get_consumer_lag(&self, topic: &str, group_id: &str) -> Result<i64, String>;
}
impl dyn EventBusBackend {
pub fn try_send<T: BusMessage>(&self, event: &T, topic: &str, options: SendOptions<'_>) -> bool {
match serde_json::to_vec(event) {
Ok(serialized) => self.try_send_serialized(&serialized, topic, options, None),
Err(_) => false, }
}
}