use babbel_bencode::{parse_bytes, Node};
fn main() {
println!("=== Validation Helpers Demo ===\n");
println!("1. Validating torrent metadata:");
let torrent_data = b"d8:announce35:http://tracker.example.com/announce4:infod6:lengthi1234567e4:name10:example.txt12:piece lengthi262144e6:pieces20:fake_hash_data_hereee";
match parse_bytes(torrent_data) {
Ok(node) => match validate_torrent(&node) {
Ok(info) => {
println!(" ✓ Torrent validation passed");
println!(" - Announce: {}", info.announce);
println!(" - Name: {}", info.name);
println!(" - Size: {} bytes", info.length);
println!(" - Piece length: {}", info.piece_length);
}
Err(e) => println!(" ✗ Validation failed: {}", e),
},
Err(e) => println!(" ✗ Parse error: {}", e),
}
println!("\n2. Handling optional fields:");
let data_with_optionals = b"d4:name4:John3:agei25e7:comment10:Some notese";
match parse_bytes(data_with_optionals) {
Ok(node) => {
match node.get_string_required("name") {
Ok(name) => println!(" ✓ Name (required): {}", name),
Err(e) => println!(" ✗ {}", e),
}
match node.get_int_required("age") {
Ok(age) => println!(" ✓ Age (required): {}", age),
Err(e) => println!(" ✗ {}", e),
}
if let Some(comment) = node.get_string_optional("comment") {
println!(" ✓ Comment (optional): {}", comment);
} else {
println!(" - Comment: not provided");
}
if let Some(email) = node.get_string_optional("email") {
println!(" ✓ Email (optional): {}", email);
} else {
println!(" - Email: not provided");
}
}
Err(e) => println!(" ✗ Parse error: {}", e),
}
println!("\n3. Type validation:");
let invalid_types = b"d4:name4:John3:age5:twentye";
match parse_bytes(invalid_types) {
Ok(node) => {
match node.get_string_required("name") {
Ok(name) => println!(" ✓ Name is string: {}", name),
Err(e) => println!(" ✗ {}", e),
}
match node.get_int_required("age") {
Ok(age) => println!(" ✓ Age is integer: {}", age),
Err(e) => println!(" ✗ Expected error: {}", e),
}
}
Err(e) => println!(" ✗ Parse error: {}", e),
}
println!("\n4. Missing required fields:");
let incomplete = b"d4:name4:Johne";
match parse_bytes(incomplete) {
Ok(node) => {
match node.get_string_required("name") {
Ok(name) => println!(" ✓ Name found: {}", name),
Err(e) => println!(" ✗ {}", e),
}
match node.get_int_required("age") {
Ok(age) => println!(" ✓ Age found: {}", age),
Err(e) => println!(" ✗ Expected error: {}", e),
}
}
Err(e) => println!(" ✗ Parse error: {}", e),
}
println!("\n5. Nested structure validation:");
let nested = b"d4:user d4:name4:John3:agei25ee8:settingsd5:theme4:dark6:notifyi1eee";
match parse_bytes(nested) {
Ok(node) => {
match node.get_dict_required("user") {
Ok(user) => {
println!(" ✓ User dictionary found");
let user_node = Node::Dictionary(user.clone());
if let Ok(name) = user_node.get_string_required("name") {
println!(" - Name: {}", name);
}
if let Ok(age) = user_node.get_int_required("age") {
println!(" - Age: {}", age);
}
}
Err(e) => println!(" ✗ {}", e),
}
match node.get_dict_required("settings") {
Ok(settings) => {
println!(" ✓ Settings dictionary found");
let settings_node = Node::Dictionary(settings.clone());
if let Some(theme) = settings_node.get_string_optional("theme") {
println!(" - Theme: {}", theme);
}
if let Some(notify) = settings_node.get_int_optional("notify") {
println!(
" - Notifications: {}",
if notify == 1 { "enabled" } else { "disabled" }
);
}
}
Err(e) => println!(" ✗ {}", e),
}
}
Err(e) => println!(" ✗ Parse error: {}", e),
}
println!("\n=== Advantages of Validation Helpers ===");
println!("✓ Clear error messages with field names");
println!("✓ Explicit required vs optional field handling");
println!("✓ Type-safe extraction with compile-time guarantees");
println!("✓ Reduces boilerplate validation code");
println!("✓ Consistent error handling across the codebase");
}
struct TorrentInfo {
announce: String,
name: String,
length: i64,
piece_length: i64,
}
fn validate_torrent(node: &Node) -> Result<TorrentInfo, String> {
let announce = node.get_string_required("announce")?.to_string();
let info_dict = node.get_dict_required("info")?;
let info = Node::Dictionary(info_dict.clone());
let name = info.get_string_required("name")?.to_string();
let length = info.get_int_required("length")?;
let piece_length = info.get_int_required("piece length")?;
let _pieces = info.get_string_required("pieces")?;
if length <= 0 {
return Err("File length must be positive".to_string());
}
if piece_length <= 0 {
return Err("Piece length must be positive".to_string());
}
Ok(TorrentInfo {
announce,
name,
length,
piece_length,
})
}