Add parsing and test

This commit is contained in:
Mikhail 2024-04-30 14:16:52 +02:00
parent 99be4fca68
commit 1996c3d6c7
2 changed files with 148 additions and 21 deletions

View File

@ -1,5 +1,7 @@
#![feature(coroutines, coroutine_trait, type_alias_impl_trait, impl_trait_in_assoc_type)] #![feature(coroutines, coroutine_trait, type_alias_impl_trait, impl_trait_in_assoc_type)]
extern crate core;
pub mod bind; pub mod bind;
pub mod client; pub mod client;
pub mod disco; pub mod disco;

View File

@ -1,26 +1,110 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use quick_xml::events::Event; use quick_xml::events::Event;
use quick_xml::name::{Namespace, ResolveResult}; use quick_xml::name::{Namespace, ResolveResult};
use std::io::Read;
use crate::xml::*; use crate::xml::*;
pub const XMLNS: &'static str = "urn:xmpp:mam:2"; pub const MAM_XMLNS: &'static str = "urn:xmpp:mam:2";
pub const DATA_XMLNS: &'static str = "jabber:x:data";
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub struct MessageArchiveRequest; pub struct MessageArchiveRequest {
x: Option<X>,
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct X {
fields: Vec<Field>,
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Field {
values: Vec<String>,
}
impl FromXmlTag for X {
const NAME: &'static str = "x";
const NS: &'static str = DATA_XMLNS;
}
impl FromXmlTag for MessageArchiveRequest { impl FromXmlTag for MessageArchiveRequest {
const NAME: &'static str = "query"; const NAME: &'static str = "query";
const NS: &'static str = XMLNS; const NS: &'static str = MAM_XMLNS;
}
impl FromXml for X {
type P = impl Parser<Output = Result<Self>>;
fn parse() -> Self::P {
|(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
println!("X::parse {:?}", event);
let bytes = match event {
Event::Start(bytes) if bytes.name().0 == X::NAME.as_bytes() => bytes,
Event::Empty(bytes) if bytes.name().0 == X::NAME.as_bytes() => return Ok(X { fields: vec![] }),
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
};
let mut fields = vec![];
loop {
(namespace, event) = yield;
match event {
Event::Start(bytes) => {
// start of <field>
let mut values = vec![];
loop {
(namespace, event) = yield;
match event {
Event::Start(bytes) if bytes.name().0 == b"value" => {
// start of <value>
}
Event::End(bytes) if bytes.name().0 == b"field" => {
// end of </field>
break;
}
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
}
(namespace, event) = yield;
let text: String = match event {
Event::Text(bytes) => {
// text inside <value></value>
String::from_utf8(bytes.to_vec())?
}
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
};
(namespace, event) = yield;
match event {
Event::End(bytes) if bytes.name().0 == b"value" => {
// end of </value>
}
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
}
values.push(text);
}
fields.push(Field { values })
}
Event::End(bytes) if bytes.name().0 == X::NAME.as_bytes() => {
// end of <x/>
return Ok(X { fields });
}
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
}
}
}
}
} }
impl FromXml for MessageArchiveRequest { impl FromXml for MessageArchiveRequest {
type P = impl Parser<Output = Result<Self>>; type P = impl Parser<Output = Result<Self>>;
fn parse() -> Self::P { fn parse() -> Self::P {
|(namespace, event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> { |(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
let Event::Start(bytes) = event else { println!("MessageArchiveRequest::parse {:?}", event);
return Err(anyhow!("Unexpected XML event: {event:?}"));
let bytes = match event {
Event::Empty(_) => return Ok(MessageArchiveRequest { x: None }),
Event::Start(bytes) => bytes,
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
}; };
if bytes.name().0 != MessageArchiveRequest::NAME.as_bytes() { if bytes.name().0 != MessageArchiveRequest::NAME.as_bytes() {
return Err(anyhow!("Unexpected XML tag: {:?}", bytes.name())); return Err(anyhow!("Unexpected XML tag: {:?}", bytes.name()));
@ -28,19 +112,20 @@ impl FromXml for MessageArchiveRequest {
let ResolveResult::Bound(Namespace(ns)) = namespace else { let ResolveResult::Bound(Namespace(ns)) = namespace else {
return Err(anyhow!("No namespace provided")); return Err(anyhow!("No namespace provided"));
}; };
if ns != XMLNS.as_bytes() { if ns != MAM_XMLNS.as_bytes() {
return Err(anyhow!("Incorrect namespace")); return Err(anyhow!("Incorrect namespace"));
} }
loop { (namespace, event) = yield;
let (namespace, event) = yield;
match event { match event {
Event::End(bytes) if bytes.name().0 == MessageArchiveRequest::NAME.as_bytes() => { Event::End(bytes) if bytes.name().0 == MessageArchiveRequest::NAME.as_bytes() => {
break; Ok(MessageArchiveRequest { x: None })
} }
_ => return Err(anyhow!("Unexpected XML event: {event:?}")), Event::Start(bytes) | Event::Empty(bytes) if bytes.name().0 == X::NAME.as_bytes() => {
let x = delegate_parsing!(X, namespace, event)?;
Ok(MessageArchiveRequest { x: Some(x) })
} }
_ => Err(anyhow!("Unexpected XML event: {event:?}")),
} }
Ok(MessageArchiveRequest)
} }
} }
} }
@ -50,22 +135,62 @@ impl MessageArchiveRequest {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::bind::{Jid, Name, Server};
use crate::client::{Iq, IqType}; use crate::client::{Iq, IqType};
#[test] #[test]
fn test_parse() { fn test_parse_archive_query() {
let input = r#"<query xmlns="urn:xmpp:mam:2"></query>"#; let input = r#"<iq to='pubsub.shakespeare.lit' type='set' id='juliet1'><query xmlns='urn:xmpp:mam:2' queryid='f28'/></iq>"#;
let result: Iq<MessageArchiveRequest> = parse(input).unwrap(); let result: Iq<MessageArchiveRequest> = parse(input).unwrap();
assert_eq!( assert_eq!(
result, result,
Iq { Iq {
from: None, from: None,
id: "mam_1".to_string(), id: "juliet1".to_string(),
to: None, to: Option::from(Jid {
r#type: IqType::Get, name: None,
body: MessageArchiveRequest, server: Server("pubsub.shakespeare.lit".into()),
resource: None,
}),
r#type: IqType::Set,
body: MessageArchiveRequest { x: None },
} }
); );
} }
#[test]
fn test_parse_query_messages_from_jid() {
let input = r#"<iq type='set' id='juliet1'><query xmlns='urn:xmpp:mam:2'><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>value1</value></field><field var='with'><value>juliet@capulet.lit</value></field></x></query></iq>"#;
let result: Iq<MessageArchiveRequest> = parse(input).unwrap();
assert_eq!(
result,
Iq {
from: None,
id: "juliet1".to_string(),
to: None,
r#type: IqType::Set,
body: MessageArchiveRequest {
x: Some(X {
fields: vec![
Field {
values: vec!["value1".to_string()],
},
Field {
values: vec!["juliet@capulet.lit".to_string()],
},
]
})
},
}
);
}
#[test]
fn test_parse_query_messages_from_jid_with_unclosed_tag() {
let input = r#"<iq type='set' id='juliet1'><query xmlns='urn:xmpp:mam:2'><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>value1</value></field><field var='with'><value>juliet@capulet.lit</value></field></query></iq>"#;
assert!(parse::<Iq<MessageArchiveRequest>>(input).is_err())
}
} }