bdecode 0.1.0

A Bencode decoder in Rust which uses the same algorithm as libtorrent.
Documentation
use url::Url;

use bdecode::bdecode;

use std::collections::HashSet;

fn test_torrent_file_bytes(bytes: &[u8]) {
    let torrent = bdecode(bytes).unwrap();
    let top_level = torrent.get_root().as_dict().unwrap();

    let mut top_level_keys = HashSet::new();
    for i in 0..top_level.len() {
        let (key, _value) = top_level.get(i).unwrap();
        top_level_keys.insert(String::from_utf8(key.to_vec()).unwrap());
    }
    assert!(top_level_keys.contains("announce"));
    assert!(top_level_keys.contains("announce-list"));
    assert!(top_level_keys.contains("comment"));
    assert!(top_level_keys.contains("created by"));
    assert!(top_level_keys.contains("creation date"));
    assert!(top_level_keys.contains("encoding"));
    assert!(top_level_keys.contains("info"));
    assert!(top_level_keys.len() == 7);

    // Check announce list-of-lists without using iterators
    let announce_list = top_level.find(b"announce-list").unwrap().as_list().unwrap();
    for x in 0..announce_list.len() {
        let alternatives = announce_list.get(x).unwrap().as_list().unwrap();
        let num_alternatives = alternatives.len();
        assert!(num_alternatives > 0);
        for y in 0..num_alternatives {
            let alternative = alternatives.get(y).unwrap();
            // Make sure it's a valid URL with scheme UDP or HTTP
            let url_buf = alternative.as_string().unwrap().as_bytes();
            let url_string = String::from_utf8(url_buf.to_vec()).unwrap();
            let parsed_url = Url::parse(&url_string).unwrap();
            assert!((parsed_url.scheme() == "udp") || (parsed_url.scheme() == "http"));
        }
    }

    // Do the same thing except use iterator syntax
    for any in announce_list.iter() {
        let alternatives = any.as_list().unwrap();
        for alternative in alternatives.iter() {
            println!("{:?}", alternative);
            // Make sure it's a valid URL with scheme UDP or HTTP
            let url_buf = alternative.as_string().unwrap().as_bytes();
            let url_string = String::from_utf8(url_buf.to_vec()).unwrap();
            let parsed_url = Url::parse(&url_string).unwrap();
            assert!((parsed_url.scheme() == "udp") || (parsed_url.scheme() == "http"));
        }
    }

    // Check that encoding is utf-8
    let encoding = top_level.find(b"encoding").unwrap().as_string().unwrap();
    assert_eq!(encoding.as_bytes(), b"utf-8");

    // Check that creation time is above the year 2000
    let creation_date = top_level.find(b"creation date").unwrap().as_int().unwrap();
    assert!(creation_date.as_i64().unwrap() >= 946684800);

    println!("{:#?}", top_level);
}

macro_rules! test_torrent_file {
    ($path:expr) => {
        let bytes = include_bytes!($path);
        test_torrent_file_bytes(&bytes[..]);
    };
}

#[test]
fn test_kon() {
    test_torrent_file!(
        "../props/[ToishY] K-ON - THE COMPLETE SAGA (BD 1920x1080 x.264 FLAC).torrent"
    );
}

#[test]
fn test_hibike_euphonium() {
    test_torrent_file!(
        "../props/[ToishY] Hibike! Euphonium - THE COMPLETE SAGA (BD 1920x1080 x264 FLAC).torrent"
    );
}

#[test]
fn test_touhou_lossless_collection() {
    test_torrent_file!("../props/Touhou lossless music collection.torrent");
}