Skip to content

Commit dc8fc81

Browse files
committed
refactor: Change tags to labels in Target struct and update related logic
- Replace the `tags` field in the `Target` struct with a `labels` field of type `HashMap<String, String>`. - Update the configuration handling and response generation to accommodate the new labels structure. - Modify tests to reflect changes in the configuration and ensure correct label processing.
1 parent 215014a commit dc8fc81

File tree

2 files changed

+102
-90
lines changed

2 files changed

+102
-90
lines changed

src/config.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clap::Parser;
22
use serde::{Deserialize, Serialize};
3+
use std::collections::HashMap;
34
use std::fmt;
45
use std::fs::File;
56
use std::io::Read;
@@ -14,12 +15,14 @@ pub struct Config {
1415
pub struct Target {
1516
pub module: String,
1617
pub url: String,
17-
#[serde(default = "default_tags")]
18-
pub tags: Vec<String>,
18+
#[serde(default = "default_labels")]
19+
pub labels: HashMap<String, String>,
1920
}
2021

21-
fn default_tags() -> Vec<String> {
22-
vec!["default".to_string()]
22+
fn default_labels() -> HashMap<String, String> {
23+
let mut labels = HashMap::new();
24+
labels.insert("default".to_string(), "true".to_string());
25+
labels
2326
}
2427

2528
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -35,8 +38,8 @@ impl fmt::Display for Config {
3538
for target in &self.target {
3639
writeln!(
3740
f,
38-
" Module: {}, URL: {}, Tags: {:?}",
39-
target.module, target.url, target.tags
41+
" Module: {}, URL: {}, Labels: {:?}",
42+
target.module, target.url, target.labels
4043
)?;
4144
}
4245

src/router.rs

Lines changed: 93 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,27 @@ use std::collections::HashMap;
88
const ENDPOINT_URL: &str = "__endpoint__url";
99
const ENDPOINT_NAME: &str = "__endpoint__name";
1010
const ENDPOINT_GEOHASH: &str = "__endpoint__geohash";
11-
const TARGET_TAG: &str = "__target_tag";
1211

1312
async fn handler(State(config): State<Config>) -> (StatusCode, Json<Response>) {
14-
// Group targets by tags
15-
// Map: tag -> list of targets
16-
let grouped_targets: HashMap<String, Vec<String>> = config
17-
.target
18-
.iter()
19-
.map(|target| {
20-
target
21-
.tags
22-
.iter()
23-
.map(|tag| (tag.clone(), target.url.clone()))
24-
.collect::<Vec<(String, String)>>()
25-
})
26-
.flatten()
27-
.fold(HashMap::new(), |mut acc, (tag, url)| {
28-
acc.entry(tag).or_insert_with(Vec::new).push(url);
29-
acc
30-
});
31-
32-
// generate config for each endpoint and tag combination
13+
// Generate config for each target and endpoint combination
3314
let resp: Vec<FileConfig> = config
3415
.endpoint
3516
.iter()
36-
.map(|endpoint| {
37-
grouped_targets.iter().map(|(tag, targets)| FileConfig {
38-
targets: targets.clone(),
39-
labels: HashMap::from([
17+
.flat_map(|endpoint| {
18+
config.target.iter().map(|target| {
19+
let mut labels = HashMap::from([
4020
(ENDPOINT_URL.to_string(), endpoint.address.clone()),
4121
(ENDPOINT_NAME.to_string(), endpoint.name.clone()),
4222
(ENDPOINT_GEOHASH.to_string(), endpoint.geohash.clone()),
43-
(TARGET_TAG.to_string(), tag.clone()),
44-
]),
23+
]);
24+
labels.extend(target.labels.clone());
25+
26+
FileConfig {
27+
targets: vec![target.url.clone()],
28+
labels,
29+
}
4530
})
4631
})
47-
.flatten()
4832
.collect();
4933

5034
(StatusCode::OK, Json(resp))
@@ -66,22 +50,33 @@ mod tests {
6650

6751
// 创建测试用的模拟配置
6852
fn create_test_config() -> crate::config::Config {
53+
use std::collections::HashMap;
54+
6955
crate::config::Config {
7056
target: vec![
7157
crate::config::Target {
7258
module: "http_2xx".to_string(),
7359
url: "https://example1.com".to_string(),
74-
tags: vec!["web".to_string(), "api".to_string()],
60+
labels: HashMap::from([
61+
("type".to_string(), "web".to_string()),
62+
("env".to_string(), "prod".to_string()),
63+
]),
7564
},
7665
crate::config::Target {
7766
module: "http_2xx".to_string(),
7867
url: "https://example2.com".to_string(),
79-
tags: vec!["web".to_string()],
68+
labels: HashMap::from([
69+
("type".to_string(), "web".to_string()),
70+
("env".to_string(), "staging".to_string()),
71+
]),
8072
},
8173
crate::config::Target {
8274
module: "http_2xx".to_string(),
8375
url: "https://api.example.com".to_string(),
84-
tags: vec!["api".to_string()],
76+
labels: HashMap::from([
77+
("type".to_string(), "api".to_string()),
78+
("env".to_string(), "prod".to_string()),
79+
]),
8580
},
8681
],
8782
endpoint: vec![
@@ -139,7 +134,8 @@ mod tests {
139134
assert!(config.labels.contains_key(ENDPOINT_URL));
140135
assert!(config.labels.contains_key(ENDPOINT_NAME));
141136
assert!(config.labels.contains_key(ENDPOINT_GEOHASH));
142-
assert!(config.labels.contains_key(TARGET_TAG));
137+
// 验证有 target 的 labels
138+
assert!(config.labels.contains_key("type"));
143139
assert!(!config.targets.is_empty());
144140
}
145141
}
@@ -158,23 +154,23 @@ mod tests {
158154
// 验证响应结构
159155
assert!(!json_response.is_empty());
160156

161-
// 验证标签分组逻辑
157+
// 验证标签逻辑
162158
let web_configs: Vec<_> = json_response
163159
.iter()
164-
.filter(|config| config.labels.get(TARGET_TAG) == Some(&"web".to_string()))
160+
.filter(|config| config.labels.get("type") == Some(&"web".to_string()))
165161
.collect();
166162
let api_configs: Vec<_> = json_response
167163
.iter()
168-
.filter(|config| config.labels.get(TARGET_TAG) == Some(&"api".to_string()))
164+
.filter(|config| config.labels.get("type") == Some(&"api".to_string()))
169165
.collect();
170166

171-
// web tag 应该包含 2 个 targets (example1.com 和 example2.com)
172-
assert_eq!(web_configs.len(), 2); // 2 endpoints * 1 web tag group
173-
assert!(web_configs.iter().any(|config| config.targets.len() == 2));
167+
// web type 应该有 4 个配置 (2 endpoints * 2 web targets)
168+
assert_eq!(web_configs.len(), 4); // 2 endpoints * 2 web targets
169+
assert!(web_configs.iter().all(|config| config.targets.len() == 1));
174170

175-
// api tag 应该包含 2 个 targets (example1.com 和 api.example.com)
176-
assert_eq!(api_configs.len(), 2); // 2 endpoints * 1 api tag group
177-
assert!(api_configs.iter().any(|config| config.targets.len() == 2));
171+
// api type 应该有 2 个配置 (2 endpoints * 1 api target)
172+
assert_eq!(api_configs.len(), 2); // 2 endpoints * 1 api target
173+
assert!(api_configs.iter().all(|config| config.targets.len() == 1));
178174
}
179175

180176
#[tokio::test]
@@ -186,7 +182,10 @@ mod tests {
186182
test_config.target.push(crate::config::Target {
187183
module: "http_2xx".to_string(),
188184
url: "https://test3.com".to_string(),
189-
tags: vec!["monitoring".to_string()],
185+
labels: HashMap::from([
186+
("type".to_string(), "monitoring".to_string()),
187+
("env".to_string(), "prod".to_string()),
188+
]),
190189
});
191190

192191
test_config.endpoint.push(crate::config::Endpoint {
@@ -206,13 +205,13 @@ mod tests {
206205
// 验证有监控标签的配置
207206
let monitoring_configs: Vec<_> = json_response
208207
.iter()
209-
.filter(|config| config.labels.get(TARGET_TAG) == Some(&"monitoring".to_string()))
208+
.filter(|config| config.labels.get("type") == Some(&"monitoring".to_string()))
210209
.collect();
211210

212-
assert_eq!(monitoring_configs.len(), 3); // 3 endpoints * 1 monitoring tag group
211+
assert_eq!(monitoring_configs.len(), 3); // 3 endpoints * 1 monitoring target
213212
assert!(monitoring_configs
214213
.iter()
215-
.any(|config| config.targets.len() == 1));
214+
.all(|config| config.targets.len() == 1));
216215
}
217216

218217
#[tokio::test]
@@ -235,16 +234,16 @@ mod tests {
235234

236235
#[tokio::test]
237236
async fn test_single_target_multiple_tags() {
238-
// 测试单个 target 有多个 tags 的情况
237+
// 测试单个 target 有多个 labels 的情况
239238
let config = crate::config::Config {
240239
target: vec![crate::config::Target {
241240
module: "http_2xx".to_string(),
242241
url: "https://multi-tag.com".to_string(),
243-
tags: vec![
244-
"web".to_string(),
245-
"api".to_string(),
246-
"monitoring".to_string(),
247-
],
242+
labels: HashMap::from([
243+
("type".to_string(), "web".to_string()),
244+
("env".to_string(), "prod".to_string()),
245+
("service".to_string(), "api".to_string()),
246+
]),
248247
}],
249248
endpoint: vec![crate::config::Endpoint {
250249
address: "test.example.com:443".to_string(),
@@ -261,43 +260,55 @@ mod tests {
261260

262261
let json_response: Response = response.json();
263262

264-
// 应该有 3 个配置项(每个 tag 一个
265-
assert_eq!(json_response.len(), 3);
263+
// 应该有 1 个配置项(1 endpoint * 1 target
264+
assert_eq!(json_response.len(), 1);
266265

267-
// 验证每个配置项都包含相同的 target
268-
for config in &json_response {
269-
assert_eq!(config.targets.len(), 1);
270-
assert_eq!(config.targets[0], "https://multi-tag.com");
271-
}
266+
// 验证配置项包含 target 和所有 labels
267+
let config = &json_response[0];
268+
assert_eq!(config.targets.len(), 1);
269+
assert_eq!(config.targets[0], "https://multi-tag.com");
270+
assert_eq!(config.labels.get("type"), Some(&"web".to_string()));
271+
assert_eq!(config.labels.get("env"), Some(&"prod".to_string()));
272+
assert_eq!(config.labels.get("service"), Some(&"api".to_string()));
272273
}
273274

274275
#[test]
275-
fn test_target_grouping_logic() {
276+
fn test_target_processing_logic() {
276277
let test_config = create_test_config();
277-
let mut grouped_targets: HashMap<String, Vec<String>> = HashMap::new();
278-
279-
// 测试分组逻辑
280-
for target in test_config.target.iter() {
281-
for tag in &target.tags {
282-
grouped_targets
283-
.entry(tag.clone())
284-
.or_default()
285-
.push(target.url.clone());
278+
279+
// 测试处理逻辑:每个 target 和 endpoint 组合生成一个 FileConfig
280+
let mut file_configs = Vec::new();
281+
282+
for endpoint in &test_config.endpoint {
283+
for target in &test_config.target {
284+
let mut labels = HashMap::from([
285+
(ENDPOINT_URL.to_string(), endpoint.address.clone()),
286+
(ENDPOINT_NAME.to_string(), endpoint.name.clone()),
287+
(ENDPOINT_GEOHASH.to_string(), endpoint.geohash.clone()),
288+
]);
289+
290+
for (key, value) in &target.labels {
291+
labels.insert(key.clone(), value.clone());
292+
}
293+
294+
file_configs.push(FileConfig {
295+
targets: vec![target.url.clone()],
296+
labels,
297+
});
286298
}
287299
}
288300

289-
// 验证分组结果
290-
assert_eq!(grouped_targets.len(), 2); // web 和 api 两个标签
291-
292-
let web_targets = grouped_targets.get("web").unwrap();
293-
assert_eq!(web_targets.len(), 2);
294-
assert!(web_targets.contains(&"https://example1.com".to_string()));
295-
assert!(web_targets.contains(&"https://example2.com".to_string()));
301+
// 验证处理结果 - 应该有 6 个配置 (2 endpoints * 3 targets)
302+
assert_eq!(file_configs.len(), 6);
296303

297-
let api_targets = grouped_targets.get("api").unwrap();
298-
assert_eq!(api_targets.len(), 2);
299-
assert!(api_targets.contains(&"https://example1.com".to_string()));
300-
assert!(api_targets.contains(&"https://api.example.com".to_string()));
304+
// 验证每个配置都包含正确的 target 和 labels
305+
for config in &file_configs {
306+
assert_eq!(config.targets.len(), 1); // 每个配置只有一个 target
307+
assert!(config.labels.contains_key(ENDPOINT_URL));
308+
assert!(config.labels.contains_key(ENDPOINT_NAME));
309+
assert!(config.labels.contains_key(ENDPOINT_GEOHASH));
310+
assert!(config.labels.contains_key("type")); // target 的 label
311+
}
301312
}
302313

303314
#[test]
@@ -310,20 +321,18 @@ mod tests {
310321
labels.insert(ENDPOINT_URL.to_string(), "test.example.com:443".to_string());
311322
labels.insert(ENDPOINT_NAME.to_string(), "TestEndpoint".to_string());
312323
labels.insert(ENDPOINT_GEOHASH.to_string(), "test_hash".to_string());
313-
labels.insert(TARGET_TAG.to_string(), "test_tag".to_string());
324+
labels.insert("type".to_string(), "web".to_string());
325+
labels.insert("env".to_string(), "prod".to_string());
314326

315327
let file_config = FileConfig { targets, labels };
316328

317329
assert_eq!(file_config.targets.len(), 2);
318-
assert_eq!(file_config.labels.len(), 4);
330+
assert_eq!(file_config.labels.len(), 5);
319331
assert_eq!(
320332
file_config.labels.get(ENDPOINT_NAME),
321333
Some(&"TestEndpoint".to_string())
322334
);
323-
assert_eq!(
324-
file_config.labels.get(TARGET_TAG),
325-
Some(&"test_tag".to_string())
326-
);
335+
assert_eq!(file_config.labels.get("type"), Some(&"web".to_string()));
327336
}
328337

329338
#[tokio::test]

0 commit comments

Comments
 (0)