use std::str::FromStr; use minidom::Element; use base64; use error::Error; use ns; #[derive(Debug, Clone, PartialEq)] pub enum Stanza { Iq, Message, } impl Default for Stanza { fn default() -> Stanza { Stanza::Iq } } impl FromStr for Stanza { type Err = Error; fn from_str(s: &str) -> Result { if s == "iq" { Ok(Stanza::Iq) } else if s == "message" { Ok(Stanza::Message) } else { Err(Error::ParseError("Unknown 'stanza' attribute.")) } } } #[derive(Debug, Clone)] pub enum IBB { Open { block_size: u16, sid: String, stanza: Stanza, }, Data { seq: u16, sid: String, data: Vec, }, Close { sid: String, }, } fn optional_attr(root: &Element, attr: &str) -> Option { root.attr(attr) .and_then(|value| value.parse().ok()) } fn required_attr(root: &Element, attr: &str, err: Error) -> Result { optional_attr(root, attr).ok_or(err) } pub fn parse_ibb(root: &Element) -> Result { if root.is("open", ns::IBB) { let block_size = required_attr(root, "block-size", Error::ParseError("Required attribute 'block-size' missing in open element."))?; let sid = required_attr(root, "sid", Error::ParseError("Required attribute 'sid' missing in open element."))?; let stanza = root.attr("stanza") .and_then(|value| value.parse().ok()) .unwrap_or_default(); for _ in root.children() { return Err(Error::ParseError("Unknown child in open element.")); } Ok(IBB::Open { block_size: block_size, sid: sid, stanza: stanza }) } else if root.is("data", ns::IBB) { let seq = required_attr(root, "seq", Error::ParseError("Required attribute 'seq' missing in data element."))?; let sid = required_attr(root, "sid", Error::ParseError("Required attribute 'sid' missing in data element."))?; let data = base64::decode(&root.text())?; for _ in root.children() { return Err(Error::ParseError("Unknown child in data element.")); } Ok(IBB::Data { seq: seq, sid: sid, data: data }) } else if root.is("close", ns::IBB) { let sid = required_attr(root, "sid", Error::ParseError("Required attribute 'sid' missing in data element."))?; for _ in root.children() { return Err(Error::ParseError("Unknown child in close element.")); } Ok(IBB::Close { sid: sid, }) } else { Err(Error::ParseError("This is not an ibb element.")) } } #[cfg(test)] mod tests { use minidom::Element; use error::Error; use ibb; #[test] fn test_simple() { let elem: Element = "".parse().unwrap(); let open = ibb::parse_ibb(&elem).unwrap(); match open { ibb::IBB::Open { block_size, sid, stanza } => { assert_eq!(block_size, 3); assert_eq!(sid, "coucou"); assert_eq!(stanza, ibb::Stanza::Iq); }, _ => panic!(), } let elem: Element = "AAAA".parse().unwrap(); let data = ibb::parse_ibb(&elem).unwrap(); match data { ibb::IBB::Data { seq, sid, data } => { assert_eq!(seq, 0); assert_eq!(sid, "coucou"); assert_eq!(data, vec!(0, 0, 0)); }, _ => panic!(), } let elem: Element = "".parse().unwrap(); let close = ibb::parse_ibb(&elem).unwrap(); match close { ibb::IBB::Close { sid } => { assert_eq!(sid, "coucou"); }, _ => panic!(), } } #[test] fn test_invalid() { let elem: Element = "".parse().unwrap(); let error = ibb::parse_ibb(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Required attribute 'block-size' missing in open element."); // TODO: maybe make a better error message here. let elem: Element = "".parse().unwrap(); let error = ibb::parse_ibb(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Required attribute 'block-size' missing in open element."); let elem: Element = "".parse().unwrap(); let error = ibb::parse_ibb(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Required attribute 'sid' missing in open element."); } #[test] #[ignore] fn test_invalid_stanza() { let elem: Element = "".parse().unwrap(); let error = ibb::parse_ibb(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Wrong value for 'stanza' attribute in open."); } }