use quick_xml::events::Event; use crate::prelude::*; use crate::util::xml::*; pub static XMLNS: &'static str = "jabber:client"; #[derive(PartialEq, Eq, Debug)] pub struct Message { from: Option, id: Option, to: Option, // default is Normal r#type: MessageType, lang: Option, subject: Option, body: String, } impl Message { pub fn parse() -> impl Parser> { MessageParser::Init } } #[derive(Default)] enum MessageParser { #[default] Init, Outer(MessageParserState), InSubject(MessageParserState), InBody(MessageParserState), } #[derive(Default)] struct MessageParserState { from: Option, id: Option, to: Option, r#type: MessageType, lang: Option, subject: Option, body: Option, } impl Parser for MessageParser { type Output = Result; fn consume<'a>(self: Self, event: &Event<'a>) -> Continuation { // TODO validate tag name and namespace at each stage match self { MessageParser::Init => { if let Event::Start(ref bytes) = event { let mut state: MessageParserState = Default::default(); for attr in bytes.attributes() { let attr = fail_fast!(attr); if attr.key.0 == b"from" { let value = fail_fast!(std::str::from_utf8(&*attr.value)); state.from = Some(value.to_string()) } else if attr.key.0 == b"id" { let value = fail_fast!(std::str::from_utf8(&*attr.value)); state.id = Some(value.to_string()) } else if attr.key.0 == b"to" { let value = fail_fast!(std::str::from_utf8(&*attr.value)); state.to = Some(value.to_string()) } } Continuation::Continue(MessageParser::Outer(state)) } else { Continuation::Final(Err(ffail!("Expected start"))) } } MessageParser::Outer(state) => { match event { Event::Start(ref bytes) => { if bytes.name().0 == b"subject" { Continuation::Continue(MessageParser::InSubject(state)) } else if bytes.name().0 == b"body" { Continuation::Continue(MessageParser::InBody(state)) } else { Continuation::Final(Err(ffail!("Unexpected XML tag"))) } } Event::End(_) => { if let Some(body) = state.body { Continuation::Final(Ok(Message { from: state.from, id: state.id, to: state.to, r#type: state.r#type, lang: state.lang, subject: state.subject, body, })) } else { Continuation::Final(Err(ffail!("Body not found"))) } } _ => { Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))) } } }, MessageParser::InSubject(mut state) => { match event { Event::Text(ref bytes) =>{ let subject = fail_fast!(std::str::from_utf8(&*bytes)); state.subject = Some(subject.to_string()); Continuation::Continue(MessageParser::InSubject(state)) } Event::End(_) => { Continuation::Continue(MessageParser::Outer(state)) } _ => { Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))) } } } MessageParser::InBody(mut state) => { match event { Event::Text(ref bytes) =>{ match std::str::from_utf8(&*bytes) { Ok(subject) => { state.body = Some(subject.to_string()); Continuation::Continue(MessageParser::InBody(state)) } Err(err) => Continuation::Final(Err(err.into())), } } Event::End(_) => { Continuation::Continue(MessageParser::Outer(state)) } _ => { Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))) } } } } } } #[derive(PartialEq, Eq, Debug)] pub enum MessageType { Chat, Error, Groupchat, Headline, Normal, } impl Default for MessageType { fn default() -> Self { MessageType::Normal } } impl MessageType { pub fn from_str(s: &str) -> Result { use MessageType::*; match s { "chat" => Ok(Chat), "error" => Ok(Error), "groupchat" => Ok(Groupchat), "headline" => Ok(Headline), "normal" => Ok(Normal), t => Err(ffail!("Unknown message type: {t}")), } } } #[cfg(test)] mod tests { use super::*; use quick_xml::NsReader; #[tokio::test] async fn parse_message() { let input = r#"daabbb"#; let mut reader = NsReader::from_reader(input.as_bytes()); let mut buf = vec![]; let event = reader.read_event_into_async(&mut buf).await.unwrap(); let mut parser = Message::parse().consume(&event); let result = loop { match parser { Continuation::Final(res) => break res, Continuation::Continue(next) => { parser = next.consume(&reader.read_event_into_async(&mut buf).await.unwrap()) } } } .unwrap(); assert_eq!( result, Message { from: None, id: Some("aacea".to_string()), to: Some("nikita@vlnv.dev".to_string()), r#type: MessageType::Normal, lang: None, subject: Some("daa".to_string()), body: "bbb".to_string(), } ) } }