saluki_components/config/
mrf.rs1use saluki_config::GenericConfiguration;
4use saluki_error::GenericError;
5
6const MRF_METRICS_ENDPOINT_PREFIX: &str = "https://app.mrf.";
7
8#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct MrfConfiguration {
11 enabled: bool,
12 failover_metrics: bool,
13 metric_allowlist: Vec<String>,
14 api_key: Option<String>,
15 site: Option<String>,
16 dd_url: Option<String>,
17}
18
19impl MrfConfiguration {
20 pub fn from_configuration(config: &GenericConfiguration) -> Result<Self, GenericError> {
22 Ok(Self {
23 enabled: config.try_get_typed("multi_region_failover.enabled")?.unwrap_or(false),
24 failover_metrics: config
25 .try_get_typed("multi_region_failover.failover_metrics")?
26 .unwrap_or(false),
27 metric_allowlist: config
28 .try_get_typed("multi_region_failover.metric_allowlist")?
29 .unwrap_or_default(),
30 api_key: get_non_empty_string(config, "multi_region_failover.api_key")?,
31 site: get_non_empty_string(config, "multi_region_failover.site")?,
32 dd_url: get_non_empty_string(config, "multi_region_failover.dd_url")?,
33 })
34 }
35
36 pub const fn is_enabled(&self) -> bool {
38 self.enabled
39 }
40
41 pub const fn is_metrics_forwarding_requested(&self) -> bool {
43 self.enabled && self.failover_metrics
44 }
45
46 pub(crate) const fn set_failover_metrics(&mut self, failover_metrics: bool) {
48 self.failover_metrics = failover_metrics;
49 }
50
51 pub(crate) fn set_metric_allowlist(&mut self, metric_allowlist: Vec<String>) {
53 self.metric_allowlist = metric_allowlist;
54 }
55
56 pub fn metric_allowlist(&self) -> &[String] {
58 &self.metric_allowlist
59 }
60
61 pub fn api_key(&self) -> Option<&str> {
63 self.api_key.as_deref()
64 }
65
66 pub fn metrics_endpoint_url(&self) -> Option<String> {
72 self.dd_url.clone().or_else(|| {
73 self.site
74 .as_deref()
75 .map(|site| format!("{MRF_METRICS_ENDPOINT_PREFIX}{site}"))
76 })
77 }
78
79 pub fn metrics_endpoint_override(&self) -> Option<(String, String)> {
81 if !self.enabled {
82 return None;
83 }
84
85 Some((self.metrics_endpoint_url()?, self.api_key.clone()?))
86 }
87}
88
89fn get_non_empty_string(config: &GenericConfiguration, key: &str) -> Result<Option<String>, GenericError> {
90 Ok(config
91 .try_get_typed::<String>(key)?
92 .map(|value| value.trim().to_string())
93 .filter(|value| !value.is_empty()))
94}
95
96#[cfg(test)]
97mod tests {
98 use saluki_config::ConfigurationLoader;
99 use serde_json::json;
100
101 use super::*;
102
103 async fn mrf_config_from(value: serde_json::Value) -> MrfConfiguration {
104 let (config, _) = ConfigurationLoader::for_tests(Some(value), None, false).await;
105 MrfConfiguration::from_configuration(&config).expect("MRF configuration should deserialize")
106 }
107
108 #[tokio::test]
109 async fn parses_mrf_configuration_keys() {
110 let config = mrf_config_from(json!({
111 "multi_region_failover": {
112 "enabled": true,
113 "failover_metrics": true,
114 "metric_allowlist": ["first.metric", "second.metric"],
115 "api_key": "mrf-api-key",
116 "site": "datadoghq.eu"
117 }
118 }))
119 .await;
120
121 assert!(config.is_metrics_forwarding_requested());
122 assert_eq!(config.metric_allowlist(), ["first.metric", "second.metric"]);
123 assert_eq!(config.api_key(), Some("mrf-api-key"));
124 assert_eq!(
125 config.metrics_endpoint_url().as_deref(),
126 Some("https://app.mrf.datadoghq.eu")
127 );
128 }
129
130 #[tokio::test]
131 async fn metrics_endpoint_override_requires_api_key_and_endpoint() {
132 let missing_api_key = mrf_config_from(json!({
133 "multi_region_failover": {
134 "enabled": true,
135 "failover_metrics": true,
136 "site": "datadoghq.eu"
137 }
138 }))
139 .await;
140 assert_eq!(missing_api_key.metrics_endpoint_override(), None);
141
142 let missing_endpoint = mrf_config_from(json!({
143 "multi_region_failover": {
144 "enabled": true,
145 "failover_metrics": true,
146 "api_key": "mrf-api-key"
147 }
148 }))
149 .await;
150 assert_eq!(missing_endpoint.metrics_endpoint_override(), None);
151
152 let ready = mrf_config_from(json!({
153 "multi_region_failover": {
154 "enabled": true,
155 "failover_metrics": true,
156 "api_key": "mrf-api-key",
157 "dd_url": "https://mrf.example.com"
158 }
159 }))
160 .await;
161 assert_eq!(
162 ready.metrics_endpoint_override(),
163 Some(("https://mrf.example.com".to_string(), "mrf-api-key".to_string()))
164 );
165 }
166
167 #[tokio::test]
168 async fn metrics_endpoint_override_does_not_require_failover_metrics() {
169 let config = mrf_config_from(json!({
170 "multi_region_failover": {
171 "enabled": true,
172 "failover_metrics": false,
173 "api_key": "mrf-api-key",
174 "dd_url": "https://mrf.example.com"
175 }
176 }))
177 .await;
178
179 assert!(!config.is_metrics_forwarding_requested());
180 assert_eq!(
181 config.metrics_endpoint_override(),
182 Some(("https://mrf.example.com".to_string(), "mrf-api-key".to_string()))
183 );
184 }
185
186 #[tokio::test]
187 async fn dd_url_takes_precedence_over_site() {
188 let config = mrf_config_from(json!({
189 "multi_region_failover": {
190 "site": "datadoghq.eu",
191 "dd_url": "https://custom-mrf.example.com"
192 }
193 }))
194 .await;
195
196 assert_eq!(
197 config.metrics_endpoint_url().as_deref(),
198 Some("https://custom-mrf.example.com")
199 );
200 }
201}