From 89351e956368ad3112127570ef03dd2547730ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cel=20=F0=9F=8C=B8?= Date: Sun, 22 Dec 2024 20:40:59 +0000 Subject: [PATCH] add jabber:iq:stanza parsers --- stanza/src/lib.rs | 1 + stanza/src/roster.rs | 153 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 stanza/src/roster.rs diff --git a/stanza/src/lib.rs b/stanza/src/lib.rs index f3b0dca..4120207 100644 --- a/stanza/src/lib.rs +++ b/stanza/src/lib.rs @@ -2,6 +2,7 @@ use peanuts::declaration::VersionInfo; pub mod bind; pub mod client; +pub mod roster; pub mod sasl; pub mod stanza_error; pub mod starttls; diff --git a/stanza/src/roster.rs b/stanza/src/roster.rs new file mode 100644 index 0000000..b49fcc3 --- /dev/null +++ b/stanza/src/roster.rs @@ -0,0 +1,153 @@ +use std::str::FromStr; + +use jid::JID; +use peanuts::{ + element::{FromElement, IntoElement}, + DeserializeError, Element, +}; + +pub const XMLNS: &str = "jabber:iq:roster"; + +pub struct Query { + ver: Option, + items: Vec, +} + +impl FromElement for Query { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult { + element.check_name("query")?; + element.check_namespace(XMLNS)?; + + let ver = element.attribute_opt("ver")?; + let items = element.pop_children()?; + + Ok(Self { ver, items }) + } +} + +impl IntoElement for Query { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("query", Some(XMLNS)) + .push_attribute_opt("ver", self.ver.clone()) + .push_children(self.items.clone()) + } +} + +#[derive(Clone)] +pub struct Item { + approved: Option, + ask: bool, + jid: JID, + name: Option, + subscription: Subscription, + groups: Vec, +} + +impl FromElement for Item { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult { + element.check_name("item")?; + element.check_namespace(XMLNS)?; + + let approved = element.attribute_opt("approved")?; + let ask = if let Some(result) = element.attribute_opt("ask")?.map(|v| { + if v == "subscribe" { + Ok(true) + } else { + Err(DeserializeError::FromStr(v)) + } + }) { + result? + } else { + false + }; + let jid = element.attribute("jid")?; + let name = element.attribute_opt("name")?; + let subscription = element.attribute_opt("subscription")?.unwrap_or_default(); + let groups = element.pop_children()?; + + Ok(Self { + approved, + ask, + jid, + name, + subscription, + groups, + }) + } +} + +impl IntoElement for Item { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("item", Some(XMLNS)) + .push_attribute_opt("approved", self.approved) + .push_attribute_opt( + "ask", + if self.ask { + Some("subscribe".to_string()) + } else { + None + }, + ) + .push_attribute("jid", self.jid.clone()) + .push_attribute_opt("name", self.name.clone()) + .push_attribute("subscription", self.subscription) + .push_children(self.groups.clone()) + } +} + +#[derive(Default, Clone, Copy)] +pub enum Subscription { + Both, + From, + #[default] + None, + Remove, + To, +} + +impl FromStr for Subscription { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result { + match s { + "both" => Ok(Self::Both), + "from" => Ok(Self::From), + "none" => Ok(Self::None), + "remove" => Ok(Self::Remove), + "to" => Ok(Self::To), + s => Err(DeserializeError::FromStr(s.to_string())), + } + } +} + +impl ToString for Subscription { + fn to_string(&self) -> String { + match self { + Subscription::Both => "both".to_string(), + Subscription::From => "from".to_string(), + Subscription::None => "none".to_string(), + Subscription::Remove => "remove".to_string(), + Subscription::To => "to".to_string(), + } + } +} + +#[derive(Clone)] +pub struct Group(Option); + +impl FromElement for Group { + fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult { + element.check_name("group")?; + element.check_namespace(XMLNS)?; + + let group = element.pop_value_opt()?; + + Ok(Self(group)) + } +} + +impl IntoElement for Group { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("group", Some(XMLNS)).push_text_opt(self.0.clone()) + } +}