Skip to content

Commit b985a09

Browse files
committed
step: [#220] implement TrackerSection DTO
1 parent 3234f16 commit b985a09

File tree

3 files changed

+251
-1
lines changed

3 files changed

+251
-1
lines changed

docs/implementation-plans/issue-220-test-command-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Phase 0: Architecture Fix
4545
[x] Step 0.3: Implement HttpTrackerSection DTO
4646
[x] Step 0.4: Implement HttpApiSection DTO
4747
[x] Step 0.5: Implement TrackerCoreSection DTO
48-
[ ] Step 0.6: Implement TrackerSection DTO
48+
[x] Step 0.6: Implement TrackerSection DTO
4949
[ ] Step 0.7: Update domain types to use SocketAddr
5050
[ ] Step 0.8: Update EnvironmentCreationConfig
5151
[ ] Step 0.9: Update all application imports

src/application/command_handlers/create/config/tracker/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
mod http_api_section;
88
mod http_tracker_section;
99
mod tracker_core_section;
10+
mod tracker_section;
1011
mod udp_tracker_section;
1112

1213
pub use http_api_section::HttpApiSection;
1314
pub use http_tracker_section::HttpTrackerSection;
1415
pub use tracker_core_section::{DatabaseSection, TrackerCoreSection};
16+
pub use tracker_section::TrackerSection;
1517
pub use udp_tracker_section::UdpTrackerSection;
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
//! Tracker configuration section (application DTO)
2+
//!
3+
//! This module provides the aggregated DTO for complete tracker configuration,
4+
//! used for JSON deserialization and validation before converting to domain types.
5+
6+
use serde::{Deserialize, Serialize};
7+
8+
use super::{HttpApiSection, HttpTrackerSection, TrackerCoreSection, UdpTrackerSection};
9+
use crate::application::command_handlers::create::config::errors::CreateConfigError;
10+
use crate::domain::tracker::{HttpApiConfig, HttpTrackerConfig, TrackerConfig, UdpTrackerConfig};
11+
12+
/// Tracker configuration section (application DTO)
13+
///
14+
/// Aggregates all tracker configuration sections: core, UDP trackers,
15+
/// HTTP trackers, and HTTP API.
16+
///
17+
/// # Examples
18+
///
19+
/// ```json
20+
/// {
21+
/// "core": {
22+
/// "database": {
23+
/// "driver": "sqlite3",
24+
/// "database_name": "tracker.db"
25+
/// },
26+
/// "private": false
27+
/// },
28+
/// "udp_trackers": [
29+
/// { "bind_address": "0.0.0.0:6969" }
30+
/// ],
31+
/// "http_trackers": [
32+
/// { "bind_address": "0.0.0.0:7070" }
33+
/// ],
34+
/// "http_api": {
35+
/// "bind_address": "0.0.0.0:1212",
36+
/// "admin_token": "MyAccessToken"
37+
/// }
38+
/// }
39+
/// ```
40+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
41+
pub struct TrackerSection {
42+
/// Core tracker configuration (database, privacy mode)
43+
pub core: TrackerCoreSection,
44+
/// UDP tracker instances
45+
pub udp_trackers: Vec<UdpTrackerSection>,
46+
/// HTTP tracker instances
47+
pub http_trackers: Vec<HttpTrackerSection>,
48+
/// HTTP API configuration
49+
pub http_api: HttpApiSection,
50+
}
51+
52+
impl TrackerSection {
53+
/// Converts this DTO to the domain `TrackerConfig` type.
54+
///
55+
/// # Errors
56+
///
57+
/// Returns error if any of the nested sections fail validation:
58+
/// - Invalid bind address formats
59+
/// - Invalid database configuration
60+
pub fn to_tracker_config(&self) -> Result<TrackerConfig, CreateConfigError> {
61+
let core = self.core.to_tracker_core_config()?;
62+
63+
let udp_trackers: Result<Vec<UdpTrackerConfig>, CreateConfigError> = self
64+
.udp_trackers
65+
.iter()
66+
.map(UdpTrackerSection::to_udp_tracker_config)
67+
.collect();
68+
69+
let http_trackers: Result<Vec<HttpTrackerConfig>, CreateConfigError> = self
70+
.http_trackers
71+
.iter()
72+
.map(HttpTrackerSection::to_http_tracker_config)
73+
.collect();
74+
75+
let http_api: HttpApiConfig = self.http_api.to_http_api_config()?;
76+
77+
Ok(TrackerConfig {
78+
core,
79+
udp_trackers: udp_trackers?,
80+
http_trackers: http_trackers?,
81+
http_api,
82+
})
83+
}
84+
}
85+
86+
#[cfg(test)]
87+
mod tests {
88+
use super::*;
89+
use crate::application::command_handlers::create::config::tracker::tracker_core_section::DatabaseSection;
90+
use crate::domain::tracker::DatabaseConfig;
91+
92+
#[test]
93+
fn test_tracker_section_converts_to_domain_config() {
94+
let section = TrackerSection {
95+
core: TrackerCoreSection {
96+
database: DatabaseSection::Sqlite {
97+
database_name: "tracker.db".to_string(),
98+
},
99+
private: false,
100+
},
101+
udp_trackers: vec![UdpTrackerSection {
102+
bind_address: "0.0.0.0:6969".to_string(),
103+
}],
104+
http_trackers: vec![HttpTrackerSection {
105+
bind_address: "0.0.0.0:7070".to_string(),
106+
}],
107+
http_api: HttpApiSection {
108+
bind_address: "0.0.0.0:1212".to_string(),
109+
admin_token: "MyAccessToken".to_string(),
110+
},
111+
};
112+
113+
let config = section.to_tracker_config().unwrap();
114+
115+
assert_eq!(
116+
config.core.database,
117+
DatabaseConfig::Sqlite {
118+
database_name: "tracker.db".to_string()
119+
}
120+
);
121+
assert!(!config.core.private);
122+
assert_eq!(config.udp_trackers.len(), 1);
123+
assert_eq!(config.http_trackers.len(), 1);
124+
assert_eq!(config.http_api.bind_address, "0.0.0.0:1212");
125+
}
126+
127+
#[test]
128+
fn test_tracker_section_handles_multiple_trackers() {
129+
let section = TrackerSection {
130+
core: TrackerCoreSection {
131+
database: DatabaseSection::Sqlite {
132+
database_name: "tracker.db".to_string(),
133+
},
134+
private: false,
135+
},
136+
udp_trackers: vec![
137+
UdpTrackerSection {
138+
bind_address: "0.0.0.0:6969".to_string(),
139+
},
140+
UdpTrackerSection {
141+
bind_address: "0.0.0.0:6970".to_string(),
142+
},
143+
],
144+
http_trackers: vec![
145+
HttpTrackerSection {
146+
bind_address: "0.0.0.0:7070".to_string(),
147+
},
148+
HttpTrackerSection {
149+
bind_address: "0.0.0.0:7071".to_string(),
150+
},
151+
],
152+
http_api: HttpApiSection {
153+
bind_address: "0.0.0.0:1212".to_string(),
154+
admin_token: "MyAccessToken".to_string(),
155+
},
156+
};
157+
158+
let config = section.to_tracker_config().unwrap();
159+
160+
assert_eq!(config.udp_trackers.len(), 2);
161+
assert_eq!(config.http_trackers.len(), 2);
162+
}
163+
164+
#[test]
165+
fn test_tracker_section_fails_for_invalid_bind_address() {
166+
let section = TrackerSection {
167+
core: TrackerCoreSection {
168+
database: DatabaseSection::Sqlite {
169+
database_name: "tracker.db".to_string(),
170+
},
171+
private: false,
172+
},
173+
udp_trackers: vec![UdpTrackerSection {
174+
bind_address: "invalid".to_string(),
175+
}],
176+
http_trackers: vec![],
177+
http_api: HttpApiSection {
178+
bind_address: "0.0.0.0:1212".to_string(),
179+
admin_token: "MyAccessToken".to_string(),
180+
},
181+
};
182+
183+
let result = section.to_tracker_config();
184+
185+
assert!(result.is_err());
186+
assert!(matches!(
187+
result.unwrap_err(),
188+
CreateConfigError::InvalidBindAddress { .. }
189+
));
190+
}
191+
192+
#[test]
193+
fn test_tracker_section_serialization() {
194+
let section = TrackerSection {
195+
core: TrackerCoreSection {
196+
database: DatabaseSection::Sqlite {
197+
database_name: "tracker.db".to_string(),
198+
},
199+
private: false,
200+
},
201+
udp_trackers: vec![UdpTrackerSection {
202+
bind_address: "0.0.0.0:6969".to_string(),
203+
}],
204+
http_trackers: vec![HttpTrackerSection {
205+
bind_address: "0.0.0.0:7070".to_string(),
206+
}],
207+
http_api: HttpApiSection {
208+
bind_address: "0.0.0.0:1212".to_string(),
209+
admin_token: "MyAccessToken".to_string(),
210+
},
211+
};
212+
213+
let json = serde_json::to_string(&section).unwrap();
214+
assert!(json.contains("\"driver\":\"sqlite3\""));
215+
assert!(json.contains("\"udp_trackers\""));
216+
assert!(json.contains("\"http_trackers\""));
217+
assert!(json.contains("\"http_api\""));
218+
}
219+
220+
#[test]
221+
fn test_tracker_section_deserialization() {
222+
let json = r#"{
223+
"core": {
224+
"database": {
225+
"driver": "sqlite3",
226+
"database_name": "tracker.db"
227+
},
228+
"private": true
229+
},
230+
"udp_trackers": [
231+
{ "bind_address": "0.0.0.0:6969" }
232+
],
233+
"http_trackers": [
234+
{ "bind_address": "0.0.0.0:7070" }
235+
],
236+
"http_api": {
237+
"bind_address": "0.0.0.0:1212",
238+
"admin_token": "MyAccessToken"
239+
}
240+
}"#;
241+
242+
let section: TrackerSection = serde_json::from_str(json).unwrap();
243+
244+
assert!(section.core.private);
245+
assert_eq!(section.udp_trackers.len(), 1);
246+
assert_eq!(section.http_trackers.len(), 1);
247+
}
248+
}

0 commit comments

Comments
 (0)