aster_forge_config/notification/
notifier.rs1use async_trait::async_trait;
2use std::sync::Arc;
3use tokio::sync::broadcast;
4
5use crate::{ConfigCoreError, Result};
6
7#[cfg(feature = "redis-pubsub")]
8use super::message::decode_config_reload_transport_payload;
9use super::message::{ConfigChangeEvent, ConfigReloadMessage};
10
11pub struct ConfigNotification {
13 receiver: ConfigNotificationReceiver,
14}
15
16enum ConfigNotificationReceiver {
17 InMemory(broadcast::Receiver<ConfigChangeEvent>),
18 #[cfg(feature = "redis-pubsub")]
19 Redis(aster_forge_events::RedisEventSubscription),
20}
21
22impl ConfigNotification {
23 pub(super) fn new(receiver: broadcast::Receiver<ConfigChangeEvent>) -> Self {
24 Self {
25 receiver: ConfigNotificationReceiver::InMemory(receiver),
26 }
27 }
28
29 #[cfg(feature = "redis-pubsub")]
30 fn from_redis(subscription: aster_forge_events::RedisEventSubscription) -> Self {
31 Self {
32 receiver: ConfigNotificationReceiver::Redis(subscription),
33 }
34 }
35
36 pub async fn recv(&mut self) -> Result<ConfigChangeEvent> {
42 match &mut self.receiver {
43 ConfigNotificationReceiver::InMemory(receiver) => receiver
44 .recv()
45 .await
46 .map_err(|error| ConfigCoreError::notification(error.to_string())),
47 #[cfg(feature = "redis-pubsub")]
48 ConfigNotificationReceiver::Redis(subscription) => loop {
49 let payload = subscription
50 .receive()
51 .await
52 .map_err(|error| ConfigCoreError::notification(error.to_string()))?;
53 match decode_config_reload_transport_payload(&payload) {
54 Ok(event) => return Ok(event),
55 Err(error) => {
56 tracing::warn!(%error, "failed to parse Redis config reload message");
57 }
58 }
59 },
60 }
61 }
62}
63
64#[async_trait]
66pub trait ConfigChangeNotifier: Send + Sync {
67 async fn publish_reload(&self, message: ConfigReloadMessage) -> Result<()>;
69
70 async fn subscribe(&self) -> Result<ConfigNotification>;
72}
73
74pub type SharedConfigChangeNotifier = Arc<dyn ConfigChangeNotifier>;
76
77#[derive(Debug, Clone)]
79pub struct InMemoryConfigNotifier {
80 pub(super) sender: broadcast::Sender<ConfigChangeEvent>,
81}
82
83impl InMemoryConfigNotifier {
84 #[must_use]
86 pub fn new(capacity: usize) -> Self {
87 let (sender, _) = broadcast::channel(capacity.max(1));
88 Self { sender }
89 }
90}
91
92impl Default for InMemoryConfigNotifier {
93 fn default() -> Self {
94 Self::new(128)
95 }
96}
97
98#[async_trait]
99impl ConfigChangeNotifier for InMemoryConfigNotifier {
100 async fn publish_reload(&self, message: ConfigReloadMessage) -> Result<()> {
101 let _ = self.sender.send(ConfigChangeEvent::Reload(message));
106 Ok(())
107 }
108
109 async fn subscribe(&self) -> Result<ConfigNotification> {
110 Ok(ConfigNotification::new(self.sender.subscribe()))
111 }
112}
113
114#[cfg(feature = "redis-pubsub")]
115mod redis_transport {
116 use super::{ConfigChangeNotifier, ConfigNotification, ConfigReloadMessage};
117 use crate::{ConfigCoreError, Result};
118
119 #[derive(Clone)]
121 pub struct RedisConfigChangeNotifier {
122 bus: aster_forge_events::RedisEventBus,
123 }
124
125 impl RedisConfigChangeNotifier {
126 pub fn new(client: redis::Client, channel: impl Into<String>) -> Self {
128 Self {
129 bus: aster_forge_events::RedisEventBus::from_client(client, channel),
130 }
131 }
132
133 pub fn from_url(url: &str, channel: impl Into<String>) -> Result<Self> {
139 let bus = aster_forge_events::RedisEventBus::from_url(url, channel)
140 .map_err(|error| ConfigCoreError::notification(error.to_string()))?;
141 Ok(Self { bus })
142 }
143
144 pub fn from_credentials(
150 base_url: &str,
151 username: Option<&str>,
152 password: Option<&str>,
153 channel: impl Into<String>,
154 ) -> Result<Self> {
155 let bus = aster_forge_events::RedisEventBus::from_credentials(
156 base_url, username, password, channel,
157 )
158 .map_err(|error| ConfigCoreError::notification(error.to_string()))?;
159 Ok(Self { bus })
160 }
161 }
162
163 #[async_trait::async_trait]
164 impl ConfigChangeNotifier for RedisConfigChangeNotifier {
165 async fn publish_reload(&self, message: ConfigReloadMessage) -> Result<()> {
166 let payload = message.encode()?;
167 self.bus
168 .publish(payload)
169 .await
170 .map_err(|error| ConfigCoreError::notification(error.to_string()))
171 }
172
173 async fn subscribe(&self) -> Result<ConfigNotification> {
174 let subscription = self
175 .bus
176 .subscribe()
177 .await
178 .map_err(|error| ConfigCoreError::notification(error.to_string()))?;
179 Ok(ConfigNotification::from_redis(subscription))
180 }
181 }
182}
183
184#[cfg(feature = "redis-pubsub")]
185pub use redis_transport::RedisConfigChangeNotifier;