aster_forge_config/notification/
config.rs1use serde::{Deserialize, Serialize};
2#[cfg(feature = "redis-pubsub")]
3use std::sync::Arc;
4
5use crate::{ConfigCoreError, Result};
6
7#[cfg(feature = "redis-pubsub")]
8use super::notifier::RedisConfigChangeNotifier;
9#[cfg(feature = "redis-pubsub")]
10use super::notifier::SharedConfigChangeNotifier;
11use super::runtime::ConfigSyncRuntime;
12
13pub const CONFIG_SYNC_BACKEND_DISABLED: &str = "disabled";
15pub const CONFIG_SYNC_BACKEND_REDIS: &str = "redis";
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct ConfigSyncConfig {
26 #[serde(default = "ConfigSyncConfig::default_backend")]
28 pub backend: String,
29 #[serde(default)]
31 pub endpoint: String,
32 #[serde(default = "ConfigSyncConfig::default_topic")]
35 pub topic: String,
36}
37
38impl Default for ConfigSyncConfig {
39 fn default() -> Self {
40 Self {
41 backend: Self::default_backend(),
42 endpoint: String::new(),
43 topic: Self::default_topic(),
44 }
45 }
46}
47
48impl ConfigSyncConfig {
49 pub fn default_backend() -> String {
51 CONFIG_SYNC_BACKEND_DISABLED.to_string()
52 }
53
54 pub fn default_topic() -> String {
56 "aster.config_reload".to_string()
57 }
58
59 pub fn enabled(&self) -> bool {
61 !matches!(
62 self.backend.trim().to_ascii_lowercase().as_str(),
63 "" | "disabled" | "none"
64 )
65 }
66}
67
68pub fn default_config_sync_topic(namespace: &str) -> String {
70 format!("{}.config_reload", namespace.trim())
71}
72
73pub fn build_config_sync_runtime(
79 config: &ConfigSyncConfig,
80 namespace: &str,
81) -> Result<ConfigSyncRuntime> {
82 build_config_sync_runtime_with_runtime_id(
83 config,
84 namespace,
85 aster_forge_utils::id::new_runtime_id(),
86 )
87}
88
89pub fn build_config_sync_runtime_with_runtime_id(
94 config: &ConfigSyncConfig,
95 namespace: &str,
96 runtime_id: impl Into<String>,
97) -> Result<ConfigSyncRuntime> {
98 let namespace = namespace.trim();
99 let runtime_id = runtime_id.into();
100 let topic = config_sync_topic(config, namespace);
101 match config.backend.trim().to_ascii_lowercase().as_str() {
102 "" | "disabled" | "none" => Ok(ConfigSyncRuntime::disabled_with_runtime_id(
103 namespace, runtime_id,
104 )),
105 CONFIG_SYNC_BACKEND_REDIS => {
106 build_redis_config_sync_runtime(config, namespace, runtime_id, &topic)
107 }
108 backend => Err(ConfigCoreError::invalid_value(format!(
109 "unsupported config_sync.backend '{backend}'"
110 ))),
111 }
112}
113fn config_sync_topic(config: &ConfigSyncConfig, namespace: &str) -> String {
114 let topic = config.topic.trim();
115 if topic.is_empty() || topic == ConfigSyncConfig::default_topic() {
116 default_config_sync_topic(namespace)
117 } else {
118 topic.to_string()
119 }
120}
121
122#[cfg(feature = "redis-pubsub")]
123fn build_redis_config_sync_runtime(
124 config: &ConfigSyncConfig,
125 namespace: &str,
126 runtime_id: String,
127 topic: &str,
128) -> Result<ConfigSyncRuntime> {
129 if config.endpoint.trim().is_empty() {
130 return Err(ConfigCoreError::invalid_value(
131 "config_sync.endpoint is required when config_sync.backend is redis",
132 ));
133 }
134 let notifier = RedisConfigChangeNotifier::from_url(
135 config.endpoint.trim(),
136 redis_channel_from_topic(topic),
137 )?;
138 Ok(ConfigSyncRuntime::new(
139 namespace,
140 runtime_id,
141 Arc::new(notifier) as SharedConfigChangeNotifier,
142 ))
143}
144
145#[cfg(not(feature = "redis-pubsub"))]
146fn build_redis_config_sync_runtime(
147 _config: &ConfigSyncConfig,
148 _namespace: &str,
149 _runtime_id: String,
150 _topic: &str,
151) -> Result<ConfigSyncRuntime> {
152 Err(ConfigCoreError::invalid_value(
153 "config_sync.backend 'redis' requires the redis-pubsub feature",
154 ))
155}
156
157#[cfg(any(feature = "redis-pubsub", test))]
158pub(super) fn redis_channel_from_topic(topic: &str) -> String {
159 topic.trim().replace('.', ":")
160}