diff options
Diffstat (limited to 'src/stanza/client')
-rw-r--r-- | src/stanza/client/error.rs | 4 | ||||
-rw-r--r-- | src/stanza/client/message.rs | 155 | ||||
-rw-r--r-- | src/stanza/client/mod.rs | 42 | ||||
-rw-r--r-- | src/stanza/client/presence.rs | 194 |
4 files changed, 382 insertions, 13 deletions
diff --git a/src/stanza/client/error.rs b/src/stanza/client/error.rs index fc5ed21..545b9a7 100644 --- a/src/stanza/client/error.rs +++ b/src/stanza/client/error.rs @@ -3,8 +3,8 @@ use std::str::FromStr; use peanuts::element::{FromElement, IntoElement}; use peanuts::{DeserializeError, Element}; -use crate::stanza::error::Text; -use crate::stanza::Error as StanzaError; +use crate::stanza::stanza_error::Error as StanzaError; +use crate::stanza::stanza_error::Text; use super::XMLNS; diff --git a/src/stanza/client/message.rs b/src/stanza/client/message.rs index cdfda5d..626d781 100644 --- a/src/stanza/client/message.rs +++ b/src/stanza/client/message.rs @@ -1,37 +1,186 @@ +use std::str::FromStr; + +use peanuts::{ + element::{FromElement, IntoElement}, + DeserializeError, Element, XML_NS, +}; + use crate::JID; +use super::XMLNS; + pub struct Message { from: Option<JID>, id: Option<String>, to: Option<JID>, - r#type: Option<MessageType>, + // can be omitted, if so default to normal + r#type: MessageType, + lang: Option<String>, // children subject: Option<Subject>, body: Option<Body>, thread: Option<Thread>, - lang: Option<String>, } +impl FromElement for Message { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("message")?; + element.check_namespace(XMLNS)?; + + let from = element.attribute_opt("from")?; + let id = element.attribute_opt("id")?; + let to = element.attribute_opt("to")?; + let r#type = element.attribute_opt("type")?.unwrap_or_default(); + let lang = element.attribute_opt_namespaced("lang", XML_NS)?; + + let subject = element.child_opt()?; + let body = element.child_opt()?; + let thread = element.child_opt()?; + + Ok(Message { + from, + id, + to, + r#type, + lang, + subject, + body, + thread, + }) + } +} + +impl IntoElement for Message { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("message", Some(XMLNS)) + .push_attribute_opt("from", self.from.clone()) + .push_attribute_opt("id", self.id.clone()) + .push_attribute_opt("to", self.to.clone()) + .push_attribute_opt("type", { + if self.r#type == MessageType::Normal { + None + } else { + Some(self.r#type) + } + }) + .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) + .push_child_opt(self.subject.clone()) + .push_child_opt(self.body.clone()) + .push_child_opt(self.thread.clone()) + } +} + +#[derive(Default, PartialEq, Eq, Copy, Clone)] pub enum MessageType { Chat, Error, Groupchat, Headline, + #[default] Normal, } +impl FromStr for MessageType { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "chat" => Ok(MessageType::Chat), + "error" => Ok(MessageType::Error), + "groupchat" => Ok(MessageType::Groupchat), + "headline" => Ok(MessageType::Headline), + "normal" => Ok(MessageType::Normal), + _ => Err(DeserializeError::FromStr(s.to_string())), + } + } +} + +impl ToString for MessageType { + fn to_string(&self) -> String { + match self { + MessageType::Chat => "chat".to_string(), + MessageType::Error => "error".to_string(), + MessageType::Groupchat => "groupchat".to_string(), + MessageType::Headline => "headline".to_string(), + MessageType::Normal => "normal".to_string(), + } + } +} + +#[derive(Clone)] pub struct Body { lang: Option<String>, body: Option<String>, } +impl FromElement for Body { + fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("body")?; + element.check_namespace(XMLNS)?; + + let lang = element.attribute_opt_namespaced("lang", XML_NS)?; + let body = element.pop_value_opt()?; + + Ok(Body { lang, body }) + } +} + +impl IntoElement for Body { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("body", Some(XMLNS)) + .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) + .push_text_opt(self.body.clone()) + } +} + +#[derive(Clone)] pub struct Subject { lang: Option<String>, subject: Option<String>, } +impl FromElement for Subject { + fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("subject")?; + element.check_namespace(XMLNS)?; + + let lang = element.attribute_opt_namespaced("lang", XML_NS)?; + let subject = element.pop_value_opt()?; + + Ok(Subject { lang, subject }) + } +} + +impl IntoElement for Subject { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("subject", Some(XMLNS)) + .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) + .push_text_opt(self.subject.clone()) + } +} + +#[derive(Clone)] pub struct Thread { - // TODO: NOT DONE parent: Option<String>, thread: Option<String>, } + +impl FromElement for Thread { + fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("thread")?; + element.check_namespace(XMLNS)?; + + let parent = element.attribute_opt("parent")?; + let thread = element.pop_value_opt()?; + + Ok(Thread { parent, thread }) + } +} + +impl IntoElement for Thread { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("thread", Some(XMLNS)) + .push_attribute_opt("parent", self.parent.clone()) + .push_text_opt(self.thread.clone()) + } +} diff --git a/src/stanza/client/mod.rs b/src/stanza/client/mod.rs index 7b25b15..25d7b56 100644 --- a/src/stanza/client/mod.rs +++ b/src/stanza/client/mod.rs @@ -1,6 +1,48 @@ +use iq::Iq; +use message::Message; +use peanuts::{ + element::{FromElement, IntoElement}, + DeserializeError, +}; +use presence::Presence; + +use super::stream::{self, Error as StreamError}; + pub mod error; pub mod iq; pub mod message; pub mod presence; pub const XMLNS: &str = "jabber:client"; + +pub enum Stanza { + Message(Message), + Presence(Presence), + Iq(Iq), + Error(StreamError), +} + +impl FromElement for Stanza { + fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + match element.identify() { + (Some(XMLNS), "message") => Ok(Stanza::Message(Message::from_element(element)?)), + (Some(XMLNS), "presence") => Ok(Stanza::Presence(Presence::from_element(element)?)), + (Some(XMLNS), "iq") => Ok(Stanza::Iq(Iq::from_element(element)?)), + (Some(stream::XMLNS), "error") => { + Ok(Stanza::Error(StreamError::from_element(element)?)) + } + _ => Err(DeserializeError::UnexpectedElement(element)), + } + } +} + +impl IntoElement for Stanza { + fn builder(&self) -> peanuts::element::ElementBuilder { + match self { + Stanza::Message(message) => message.builder(), + Stanza::Presence(presence) => presence.builder(), + Stanza::Iq(iq) => iq.builder(), + Stanza::Error(error) => error.builder(), + } + } +} diff --git a/src/stanza/client/presence.rs b/src/stanza/client/presence.rs index 46194f3..bcb04d4 100644 --- a/src/stanza/client/presence.rs +++ b/src/stanza/client/presence.rs @@ -1,24 +1,77 @@ -use peanuts::element::{FromElement, IntoElement}; +use std::str::FromStr; + +use peanuts::{ + element::{FromElement, IntoElement}, + DeserializeError, Element, XML_NS, +}; use crate::JID; -use super::error::Error; +use super::{error::Error, XMLNS}; pub struct Presence { from: Option<JID>, id: Option<String>, to: Option<JID>, - r#type: PresenceType, + r#type: Option<PresenceType>, lang: Option<String>, // children show: Option<Show>, status: Option<Status>, priority: Option<Priority>, + // TODO: ##other + // other: Vec<Other>, errors: Vec<Error>, - // ##other - // content: Vec<Box<dyn AsElement>>, } +impl FromElement for Presence { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("presence")?; + element.check_namespace(XMLNS)?; + + let from = element.attribute_opt("from")?; + let id = element.attribute_opt("id")?; + let to = element.attribute_opt("to")?; + let r#type = element.attribute_opt("type")?; + let lang = element.attribute_opt_namespaced("lang", XML_NS)?; + + let show = element.child_opt()?; + let status = element.child_opt()?; + let priority = element.child_opt()?; + let errors = element.children()?; + + Ok(Presence { + from, + id, + to, + r#type, + lang, + show, + status, + priority, + errors, + }) + } +} + +impl IntoElement for Presence { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("presence", Some(XMLNS)) + .push_attribute_opt("from", self.from.clone()) + .push_attribute_opt("id", self.id.clone()) + .push_attribute_opt("to", self.to.clone()) + .push_attribute_opt("type", self.r#type) + .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) + .push_child_opt(self.show) + .push_child_opt(self.status.clone()) + .push_child_opt(self.priority) + .push_children(self.errors.clone()) + } +} + +pub enum Other {} + +#[derive(Copy, Clone)] pub enum PresenceType { Error, Probe, @@ -29,6 +82,38 @@ pub enum PresenceType { Unsubscribed, } +impl FromStr for PresenceType { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "error" => Ok(PresenceType::Error), + "probe" => Ok(PresenceType::Probe), + "subscribe" => Ok(PresenceType::Subscribe), + "subscribed" => Ok(PresenceType::Subscribed), + "unavailable" => Ok(PresenceType::Unavailable), + "unsubscribe" => Ok(PresenceType::Unsubscribe), + "unsubscribed" => Ok(PresenceType::Unsubscribed), + s => Err(DeserializeError::FromStr(s.to_string())), + } + } +} + +impl ToString for PresenceType { + fn to_string(&self) -> String { + match self { + PresenceType::Error => "error".to_string(), + PresenceType::Probe => "probe".to_string(), + PresenceType::Subscribe => "subscribe".to_string(), + PresenceType::Subscribed => "subscribed".to_string(), + PresenceType::Unavailable => "unavailable".to_string(), + PresenceType::Unsubscribe => "unsubscribe".to_string(), + PresenceType::Unsubscribed => "unsubscribed".to_string(), + } + } +} + +#[derive(Copy, Clone)] pub enum Show { Away, Chat, @@ -36,13 +121,106 @@ pub enum Show { Xa, } +impl FromElement for Show { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("show")?; + element.check_namespace(XMLNS)?; + + Ok(element.pop_value()?) + } +} + +impl FromStr for Show { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "away" => Ok(Show::Away), + "chat" => Ok(Show::Chat), + "dnd" => Ok(Show::Dnd), + "xa" => Ok(Show::Xa), + s => Err(DeserializeError::FromStr(s.to_string())), + } + } +} + +impl IntoElement for Show { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("show", Some(XMLNS)).push_text(*self) + } +} + +impl ToString for Show { + fn to_string(&self) -> String { + match self { + Show::Away => "away".to_string(), + Show::Chat => "chat".to_string(), + Show::Dnd => "dnd".to_string(), + Show::Xa => "xa".to_string(), + } + } +} + +#[derive(Clone)] pub struct Status { lang: Option<String>, status: String1024, } -// minLength 1 maxLength 1024 -pub struct String1024(String); +impl FromElement for Status { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("status")?; + element.check_namespace(XMLNS)?; + + let lang = element.attribute_opt_namespaced("lang", XML_NS)?; + let status = element.pop_value()?; + + Ok(Status { lang, status }) + } +} + +impl IntoElement for Status { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("status", Some(XMLNS)) + .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) + .push_text(self.status.clone()) + } +} + +// TODO: enforce? +/// minLength 1 maxLength 1024 +#[derive(Clone)] +pub struct String1024(pub String); + +impl FromStr for String1024 { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(String1024(s.to_string())) + } +} + +impl ToString for String1024 { + fn to_string(&self) -> String { + self.0.clone() + } +} // xs:byte -pub struct Priority(u8); +#[derive(Clone, Copy)] +pub struct Priority(pub i8); + +impl FromElement for Priority { + fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("priority")?; + element.check_namespace(XMLNS)?; + + Ok(Priority(element.pop_value()?)) + } +} + +impl IntoElement for Priority { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("priority", Some(XMLNS)).push_text(self.0) + } +} |