extern crate byteorder;
use std::ascii::{AsciiExt};
use std::io::{Cursor, Write};
use self::byteorder::{LittleEndian, WriteBytesExt};
use error::{Error, Result};
pub const KIND_BINARY: u32 = 1;
pub const KIND_LOCATOR: u32 = 2;
pub const KIND_TEXT: u32 = 0;
const DENIED_KEYS: [&'static str; 4] = ["ID3", "TAG", "OggS", "MP+"];
#[derive(Debug)]
pub enum ItemValue {
Binary(Vec<u8>),
Locator(String),
Text(String),
}
#[derive(Debug)]
pub struct Item {
pub key: String,
pub value: ItemValue,
}
impl Item {
fn new<S: Into<String>>(key: S, value: ItemValue) -> Result<Item> {
let key = key.into();
let len = key.len();
if len < 2 || len > 255 {
return Err(Error::InvalidItemKeyLen);
}
if DENIED_KEYS.iter().any(|&dk| dk == key) {
return Err(Error::ItemKeyDenied);
}
if !key.chars().all(|c| c.is_ascii()) {
return Err(Error::InvalidItemKeyValue);
}
Ok(Item { key: key, value: value })
}
pub fn from_binary<S: Into<String>>(key: S, value: Vec<u8>) -> Result<Item> {
Self::new(key, ItemValue::Binary(value))
}
pub fn from_locator<S: Into<String>>(key: S, value: S) -> Result<Item> {
Self::new(key, ItemValue::Locator(value.into()))
}
pub fn from_text<S: Into<String>>(key: S, value: S) -> Result<Item> {
Self::new(key, ItemValue::Text(value.into()))
}
pub fn set_binary(&mut self, value: Vec<u8>) {
self.value = ItemValue::Binary(value);
}
pub fn set_locator<S: Into<String>>(&mut self, value: S) {
self.value = ItemValue::Locator(value.into());
}
pub fn set_text<S: Into<String>>(&mut self, value: S) {
self.value = ItemValue::Text(value.into());
}
pub fn to_vec(&self) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(Vec::<u8>::new());
let size: u32;
let flags: u32;
let value: &[u8];
match self.value {
ItemValue::Binary(ref val) => {
size = val.len() as u32;
flags = KIND_BINARY << 1;
value = val;
},
ItemValue::Locator(ref val) => {
size = val.len() as u32;
flags = KIND_LOCATOR << 1;
value = val.as_ref();
},
ItemValue::Text(ref val) => {
size = val.len() as u32;
flags = KIND_TEXT << 1;
value = val.as_ref();
}
};
try!(cursor.write_u32::<LittleEndian>(size));
try!(cursor.write_u32::<LittleEndian>(flags));
try!(cursor.write_all(self.key.as_ref()));
try!(cursor.write_u8(0));
try!(cursor.write_all(value));
Ok(cursor.into_inner())
}
}
#[cfg(test)]
mod test {
extern crate byteorder;
use std::io::{Cursor, Read};
use self::byteorder::{LittleEndian, ReadBytesExt};
use super::{Item, ItemValue, KIND_BINARY, KIND_LOCATOR, KIND_TEXT, DENIED_KEYS};
#[test]
#[should_panic(expected = "Item keys can have a length of 2 up to 255 characters")]
fn new_failed_with_bad_key_len() {
Item::from_text("k", "val").unwrap();
}
#[test]
fn new_failed_with_denied_key() {
let msg = "Not allowed are the following keys: ID3, TAG, OggS and MP+";
for key in DENIED_KEYS.iter() {
match Item::from_text(key.to_string(), "val".to_string()) {
Err(err) => {
assert_eq!(msg, format!("{}", err));
},
Ok(_) => {panic!("Unexpected item");}
};
}
}
#[test]
#[should_panic(expected = "Item key contains non-ascii characters")]
fn new_failed_with_bad_key_val() {
Item::from_text("Недопустимые символы", "val").unwrap();
}
#[test]
fn binary() {
let vec: Vec<u8> = vec!(1);
let mut item = Item::from_binary("key", vec).unwrap();
assert_eq!("key", item.key);
assert_eq!(1, match item.value {
ItemValue::Binary(ref val) => val,
_ => panic!("Invalid value")
}[0]);
let vec: Vec<u8> = vec!(0);
item.set_binary(vec);
assert_eq!(0, match item.value {
ItemValue::Binary(ref val) => val,
_ => panic!("Invalid value")
}[0]);
}
#[test]
fn locator() {
let locator = "http://hostname.com";
let mut item = Item::from_locator("key", locator).unwrap();
assert_eq!("key", item.key);
assert_eq!(locator, match item.value {
ItemValue::Locator(ref val) => val,
_ => panic!("Invalid value")
});
let locator = "http://another-hostname.com";
item.set_locator(locator);
assert_eq!(locator, match item.value {
ItemValue::Locator(ref val) => val,
_ => panic!("Invalid value")
});
}
#[test]
fn text() {
let text = "text";
let mut item = Item::from_text("key", text).unwrap();
assert_eq!("key", item.key);
assert_eq!(text, match item.value {
ItemValue::Text(ref val) => val,
_ => panic!("Invalid value")
});
let text = "another-text";
item.set_text(text);
assert_eq!(text, match item.value {
ItemValue::Text(ref val) => val,
_ => panic!("Invalid value")
});
}
#[test]
fn to_vec() {
let mut data = Cursor::new(Item::from_binary("cover", vec!(1, 2, 3)).unwrap().to_vec().unwrap());
let item_size = data.read_u32::<LittleEndian>().unwrap();
assert_eq!(3, item_size);
let item_flags = data.read_u32::<LittleEndian>().unwrap();
assert_eq!(KIND_BINARY, (item_flags & 6) >> 1);
let mut item_key = Vec::<u8>::new();
let mut k = data.read_u8().unwrap();
while k != 0 {
item_key.push(k);
k = data.read_u8().unwrap();
}
assert_eq!("cover", item_key.iter().map(|&c| c as char).collect::<String>());
let mut item_value = Vec::<u8>::with_capacity(item_size as usize);
data.take(item_size as u64).read_to_end(&mut item_value).unwrap();
assert_eq!(vec!(1, 2, 3), item_value);
let mut data = Cursor::new(Item::from_text("artist", "Artist").unwrap().to_vec().unwrap());
let item_size = data.read_u32::<LittleEndian>().unwrap();
assert_eq!(6, item_size);
let item_flags = data.read_u32::<LittleEndian>().unwrap();
assert_eq!(KIND_TEXT, (item_flags & 6) >> 1);
let mut data = Cursor::new(Item::from_locator("url", "http://test.com").unwrap().to_vec().unwrap());
let item_size = data.read_u32::<LittleEndian>().unwrap();
assert_eq!(15, item_size);
let item_flags = data.read_u32::<LittleEndian>().unwrap();
assert_eq!(KIND_LOCATOR, (item_flags & 6) >> 1);
}
}