use std::str::FromStr; use jid::JID; use peanuts::{ element::{FromElement, IntoElement}, DeserializeError, Element, }; pub const XMLNS: &str = "jabber:iq:roster"; #[derive(Debug, Clone)] pub struct Query { pub ver: Option, pub 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, Debug)] pub struct Item { /// signals subscription pre-approval (server only) pub approved: Option, /// signals subscription sub-states (server only) pub ask: bool, /// uniquely identifies item pub jid: JID, /// handle that is determined by user, not contact pub name: Option, /// state of the presence subscription pub subscription: Option, pub 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")?; 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_opt("subscription", self.subscription) .push_children(self.groups.clone()) } } #[derive(Default, Clone, Copy, Debug)] 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, Debug)] // TODO: check if should be option or not pub struct Group(pub 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()) } }