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) +    } +} | 
