Skip to content

Commit 6c86c44

Browse files
committed
step: [#220] implement UdpTrackerSection DTO
1 parent d70ab59 commit 6c86c44

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

src/application/command_handlers/create/config/errors.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ pub enum CreateConfigError {
5050
#[error("Invalid SSH port: {port} (must be between 1 and 65535)")]
5151
InvalidPort { port: u16 },
5252

53+
/// Invalid bind address format
54+
#[error("Invalid bind address '{address}': failed to parse as IP:PORT")]
55+
InvalidBindAddress {
56+
/// The invalid bind address that was provided
57+
address: String,
58+
/// The underlying parse error
59+
#[source]
60+
source: std::net::AddrParseError,
61+
},
62+
5363
/// Failed to serialize configuration template to JSON
5464
#[error("Failed to serialize configuration template to JSON")]
5565
TemplateSerializationFailed {
@@ -195,6 +205,23 @@ impl CreateConfigError {
195205
\n\
196206
Fix: Update the SSH port in your configuration to a valid port number (1-65535)."
197207
}
208+
Self::InvalidBindAddress { .. } => {
209+
"Invalid bind address format.\n\
210+
\n\
211+
Bind addresses must be in the format IP:PORT (e.g., '0.0.0.0:8080').\n\
212+
\n\
213+
Valid examples:\n\
214+
- '0.0.0.0:6969' (bind to all interfaces on port 6969)\n\
215+
- '127.0.0.1:7070' (bind to localhost on port 7070)\n\
216+
- '[::]:1212' (bind to all IPv6 interfaces on port 1212)\n\
217+
\n\
218+
Common mistakes:\n\
219+
- Missing port number (e.g., '0.0.0.0')\n\
220+
- Invalid IP address format\n\
221+
- Port number out of range (must be 1-65535)\n\
222+
\n\
223+
Fix: Update the bind_address in your configuration to use valid IP:PORT format."
224+
}
198225
Self::TemplateSerializationFailed { .. } => {
199226
"Template serialization failed.\n\
200227
\n\

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@
33
//! This module contains DTO types for tracker configuration used in
44
//! environment creation. These types use raw primitives (String) for
55
//! JSON deserialization and convert to rich domain types (`SocketAddr`).
6+
7+
mod udp_tracker_section;
8+
9+
pub use udp_tracker_section::UdpTrackerSection;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use std::net::SocketAddr;
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::application::command_handlers::create::config::errors::CreateConfigError;
6+
use crate::domain::tracker::UdpTrackerConfig;
7+
8+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9+
pub struct UdpTrackerSection {
10+
pub bind_address: String,
11+
}
12+
13+
impl UdpTrackerSection {
14+
/// Converts this DTO to a domain `UdpTrackerConfig`
15+
///
16+
/// # Errors
17+
///
18+
/// Returns `CreateConfigError::InvalidBindAddress` if the bind address cannot be parsed as a valid IP:PORT combination.
19+
pub fn to_udp_tracker_config(&self) -> Result<UdpTrackerConfig, CreateConfigError> {
20+
// Validate that the bind address can be parsed as SocketAddr
21+
let _bind_address = self.bind_address.parse::<SocketAddr>().map_err(|e| {
22+
CreateConfigError::InvalidBindAddress {
23+
address: self.bind_address.clone(),
24+
source: e,
25+
}
26+
})?;
27+
28+
// For now, keep as String since domain type still uses String
29+
// This will be updated in Step 0.7 when we enhance domain types
30+
Ok(UdpTrackerConfig {
31+
bind_address: self.bind_address.clone(),
32+
})
33+
}
34+
}
35+
36+
#[cfg(test)]
37+
mod tests {
38+
use super::*;
39+
40+
#[test]
41+
fn it_should_convert_valid_bind_address_to_udp_tracker_config() {
42+
let section = UdpTrackerSection {
43+
bind_address: "0.0.0.0:6969".to_string(),
44+
};
45+
46+
let result = section.to_udp_tracker_config();
47+
assert!(result.is_ok());
48+
49+
let config = result.unwrap();
50+
assert_eq!(config.bind_address, "0.0.0.0:6969");
51+
}
52+
53+
#[test]
54+
fn it_should_fail_for_invalid_bind_address() {
55+
let section = UdpTrackerSection {
56+
bind_address: "invalid".to_string(),
57+
};
58+
59+
let result = section.to_udp_tracker_config();
60+
assert!(result.is_err());
61+
62+
if let Err(CreateConfigError::InvalidBindAddress { address, .. }) = result {
63+
assert_eq!(address, "invalid");
64+
} else {
65+
panic!("Expected InvalidBindAddress error");
66+
}
67+
}
68+
69+
#[test]
70+
fn it_should_be_serializable() {
71+
let section = UdpTrackerSection {
72+
bind_address: "0.0.0.0:6969".to_string(),
73+
};
74+
75+
let json = serde_json::to_string(&section).unwrap();
76+
assert!(json.contains("bind_address"));
77+
assert!(json.contains("0.0.0.0:6969"));
78+
}
79+
80+
#[test]
81+
fn it_should_be_deserializable() {
82+
let json = r#"{"bind_address":"0.0.0.0:6969"}"#;
83+
let section: UdpTrackerSection = serde_json::from_str(json).unwrap();
84+
assert_eq!(section.bind_address, "0.0.0.0:6969");
85+
}
86+
}

0 commit comments

Comments
 (0)