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::config_from;
99 use serde_json::json;
100
101 use super::*;
102
103 async fn mrf_config_from(value: serde_json::Value) -> MrfConfiguration {
104 MrfConfiguration::from_configuration(&config_from(value).await).expect("MRF configuration should deserialize")
105 }
106
107 #[tokio::test]
108 async fn parses_mrf_configuration_keys() {
109 let config = mrf_config_from(json!({
110 "multi_region_failover": {
111 "enabled": true,
112 "failover_metrics": true,
113 "metric_allowlist": ["first.metric", "second.metric"],
114 "api_key": "mrf-api-key",
115 "site": "datadoghq.eu"
116 }
117 }))
118 .await;
119
120 assert!(config.is_metrics_forwarding_requested());
121 assert_eq!(config.metric_allowlist(), ["first.metric", "second.metric"]);
122 assert_eq!(config.api_key(), Some("mrf-api-key"));
123 assert_eq!(
124 config.metrics_endpoint_url().as_deref(),
125 Some("https://app.mrf.datadoghq.eu")
126 );
127 }
128
129 #[tokio::test]
130 async fn metrics_endpoint_override_requires_api_key_and_endpoint() {
131 let missing_api_key = mrf_config_from(json!({
132 "multi_region_failover": {
133 "enabled": true,
134 "failover_metrics": true,
135 "site": "datadoghq.eu"
136 }
137 }))
138 .await;
139 assert_eq!(missing_api_key.metrics_endpoint_override(), None);
140
141 let missing_endpoint = mrf_config_from(json!({
142 "multi_region_failover": {
143 "enabled": true,
144 "failover_metrics": true,
145 "api_key": "mrf-api-key"
146 }
147 }))
148 .await;
149 assert_eq!(missing_endpoint.metrics_endpoint_override(), None);
150
151 let ready = mrf_config_from(json!({
152 "multi_region_failover": {
153 "enabled": true,
154 "failover_metrics": true,
155 "api_key": "mrf-api-key",
156 "dd_url": "https://mrf.example.com"
157 }
158 }))
159 .await;
160 assert_eq!(
161 ready.metrics_endpoint_override(),
162 Some(("https://mrf.example.com".to_string(), "mrf-api-key".to_string()))
163 );
164 }
165
166 #[tokio::test]
167 async fn metrics_endpoint_override_does_not_require_failover_metrics() {
168 let config = mrf_config_from(json!({
169 "multi_region_failover": {
170 "enabled": true,
171 "failover_metrics": false,
172 "api_key": "mrf-api-key",
173 "dd_url": "https://mrf.example.com"
174 }
175 }))
176 .await;
177
178 assert!(!config.is_metrics_forwarding_requested());
179 assert_eq!(
180 config.metrics_endpoint_override(),
181 Some(("https://mrf.example.com".to_string(), "mrf-api-key".to_string()))
182 );
183 }
184
185 #[tokio::test]
186 async fn metrics_endpoint_override_is_none_when_disabled() {
187 let config = mrf_config_from(json!({
190 "multi_region_failover": {
191 "enabled": false,
192 "failover_metrics": true,
193 "api_key": "mrf-api-key",
194 "dd_url": "https://mrf.example.com"
195 }
196 }))
197 .await;
198
199 assert!(!config.is_enabled());
200 assert_eq!(config.metrics_endpoint_override(), None);
201
202 assert_eq!(
205 config.metrics_endpoint_url().as_deref(),
206 Some("https://mrf.example.com")
207 );
208 assert_eq!(config.api_key(), Some("mrf-api-key"));
209 }
210
211 #[tokio::test]
212 async fn dd_url_takes_precedence_over_site() {
213 let config = mrf_config_from(json!({
214 "multi_region_failover": {
215 "site": "datadoghq.eu",
216 "dd_url": "https://custom-mrf.example.com"
217 }
218 }))
219 .await;
220
221 assert_eq!(
222 config.metrics_endpoint_url().as_deref(),
223 Some("https://custom-mrf.example.com")
224 );
225 }
226}