diff options
author | 2024-12-04 18:18:37 +0000 | |
---|---|---|
committer | 2024-12-04 18:18:37 +0000 | |
commit | 1b91ff690488b65b552c90bd5392b9a300c8c981 (patch) | |
tree | 9c290f69b26eba0393d7bbc05ba29c28ea74a26e /stanza/src/client | |
parent | 03764f8cedb3f0a55a61be0f0a59faaa6357a83a (diff) | |
download | luz-1b91ff690488b65b552c90bd5392b9a300c8c981.tar.gz luz-1b91ff690488b65b552c90bd5392b9a300c8c981.tar.bz2 luz-1b91ff690488b65b552c90bd5392b9a300c8c981.zip |
use cargo workspace
Diffstat (limited to 'stanza/src/client')
-rw-r--r-- | stanza/src/client/error.rs | 83 | ||||
-rw-r--r-- | stanza/src/client/iq.rs | 122 | ||||
-rw-r--r-- | stanza/src/client/message.rs | 185 | ||||
-rw-r--r-- | stanza/src/client/mod.rs | 61 | ||||
-rw-r--r-- | stanza/src/client/presence.rs | 225 |
5 files changed, 676 insertions, 0 deletions
diff --git a/stanza/src/client/error.rs b/stanza/src/client/error.rs new file mode 100644 index 0000000..689953a --- /dev/null +++ b/stanza/src/client/error.rs @@ -0,0 +1,83 @@ +use std::str::FromStr; + +use peanuts::element::{FromElement, IntoElement}; +use peanuts::{DeserializeError, Element}; + +use crate::stanza_error::Error as StanzaError; +use crate::stanza_error::Text; + +use super::XMLNS; + +#[derive(Clone, Debug)] +pub struct Error { + by: Option<String>, + r#type: ErrorType, + // children (sequence) + error: StanzaError, + text: Option<Text>, +} + +impl FromElement for Error { + fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("error")?; + element.check_name(XMLNS)?; + + let by = element.attribute_opt("by")?; + let r#type = element.attribute("type")?; + let error = element.pop_child_one()?; + let text = element.pop_child_opt()?; + + Ok(Error { + by, + r#type, + error, + text, + }) + } +} + +impl IntoElement for Error { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("error", Some(XMLNS)) + .push_attribute_opt("by", self.by.clone()) + .push_attribute("type", self.r#type) + .push_child(self.error.clone()) + .push_child_opt(self.text.clone()) + } +} + +#[derive(Copy, Clone, Debug)] +pub enum ErrorType { + Auth, + Cancel, + Continue, + Modify, + Wait, +} + +impl FromStr for ErrorType { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "auth" => Ok(ErrorType::Auth), + "cancel" => Ok(ErrorType::Cancel), + "continue" => Ok(ErrorType::Continue), + "modify" => Ok(ErrorType::Modify), + "wait" => Ok(ErrorType::Wait), + _ => Err(DeserializeError::FromStr(s.to_string())), + } + } +} + +impl ToString for ErrorType { + fn to_string(&self) -> String { + match self { + ErrorType::Auth => "auth".to_string(), + ErrorType::Cancel => "cancel".to_string(), + ErrorType::Continue => "continue".to_string(), + ErrorType::Modify => "modify".to_string(), + ErrorType::Wait => "wait".to_string(), + } + } +} diff --git a/stanza/src/client/iq.rs b/stanza/src/client/iq.rs new file mode 100644 index 0000000..388979e --- /dev/null +++ b/stanza/src/client/iq.rs @@ -0,0 +1,122 @@ +use std::str::FromStr; + +use jid::JID; +use peanuts::{ + element::{FromElement, IntoElement}, + DeserializeError, Element, XML_NS, +}; + +use crate::{ + bind::{self, Bind}, + client::error::Error, +}; + +use super::XMLNS; + +pub struct Iq { + pub from: Option<JID>, + pub id: String, + pub to: Option<JID>, + pub r#type: IqType, + pub lang: Option<String>, + // children + // ##other + pub query: Option<Query>, + pub errors: Vec<Error>, +} + +#[derive(Clone)] +pub enum Query { + Bind(Bind), + Unsupported, +} + +impl FromElement for Query { + fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + match element.identify() { + (Some(bind::XMLNS), "bind") => Ok(Query::Bind(Bind::from_element(element)?)), + _ => Ok(Query::Unsupported), + } + } +} + +impl IntoElement for Query { + fn builder(&self) -> peanuts::element::ElementBuilder { + match self { + Query::Bind(bind) => bind.builder(), + // TODO: consider what to do if attempt to serialize unsupported + Query::Unsupported => todo!(), + } + } +} + +impl FromElement for Iq { + fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("iq")?; + element.check_namespace(XMLNS)?; + + let from = element.attribute_opt("from")?; + let id = element.attribute("id")?; + let to = element.attribute_opt("to")?; + let r#type = element.attribute("type")?; + let lang = element.attribute_opt_namespaced("lang", XML_NS)?; + let query = element.pop_child_opt()?; + let errors = element.pop_children()?; + + Ok(Iq { + from, + id, + to, + r#type, + lang, + query, + errors, + }) + } +} + +impl IntoElement for Iq { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("iq", Some(XMLNS)) + .push_attribute_opt("from", self.from.clone()) + .push_attribute("id", self.id.clone()) + .push_attribute_opt("to", self.to.clone()) + .push_attribute("type", self.r#type) + .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) + .push_child_opt(self.query.clone()) + .push_children(self.errors.clone()) + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum IqType { + Error, + Get, + Result, + Set, +} + +impl FromStr for IqType { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "error" => Ok(IqType::Error), + "get" => Ok(IqType::Get), + "result" => Ok(IqType::Result), + "set" => Ok(IqType::Set), + _ => Err(DeserializeError::FromStr(s.to_string())), + } + } +} + +impl ToString for IqType { + fn to_string(&self) -> String { + match self { + IqType::Error => "error".to_string(), + IqType::Get => "get".to_string(), + IqType::Result => "result".to_string(), + IqType::Set => "set".to_string(), + } + } +} diff --git a/stanza/src/client/message.rs b/stanza/src/client/message.rs new file mode 100644 index 0000000..b9d995f --- /dev/null +++ b/stanza/src/client/message.rs @@ -0,0 +1,185 @@ +use std::str::FromStr; + +use jid::JID; +use peanuts::{ + element::{FromElement, IntoElement}, + DeserializeError, Element, XML_NS, +}; + +use super::XMLNS; + +pub struct Message { + from: Option<JID>, + id: Option<String>, + to: Option<JID>, + // can be omitted, if so default to normal + r#type: MessageType, + lang: Option<String>, + // children + subject: Option<Subject>, + body: Option<Body>, + thread: Option<Thread>, +} + +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 { + 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/stanza/src/client/mod.rs b/stanza/src/client/mod.rs new file mode 100644 index 0000000..2b063d6 --- /dev/null +++ b/stanza/src/client/mod.rs @@ -0,0 +1,61 @@ +use iq::Iq; +use message::Message; +use peanuts::{ + element::{Content, ContentBuilder, FromContent, FromElement, IntoContent, 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), + OtherContent(Content), +} + +impl FromContent for Stanza { + fn from_content(content: Content) -> peanuts::element::DeserializeResult<Self> { + match content { + Content::Element(element) => Ok(Stanza::from_element(element)?), + Content::Text(_) => Ok(Stanza::OtherContent(content)), + Content::PI => Ok(Stanza::OtherContent(content)), + Content::Comment(_) => Ok(Stanza::OtherContent(content)), + } + } +} + +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 IntoContent for Stanza { + fn builder(&self) -> peanuts::element::ContentBuilder { + match self { + Stanza::Message(message) => <Message as IntoContent>::builder(message), + Stanza::Presence(presence) => <Presence as IntoContent>::builder(presence), + Stanza::Iq(iq) => <Iq as IntoContent>::builder(iq), + Stanza::Error(error) => <StreamError as IntoContent>::builder(error), + Stanza::OtherContent(_content) => ContentBuilder::Comment("other-content".to_string()), + } + } +} diff --git a/stanza/src/client/presence.rs b/stanza/src/client/presence.rs new file mode 100644 index 0000000..dd14bff --- /dev/null +++ b/stanza/src/client/presence.rs @@ -0,0 +1,225 @@ +use std::str::FromStr; + +use jid::JID; +use peanuts::{ + element::{FromElement, IntoElement}, + DeserializeError, Element, XML_NS, +}; + +use super::{error::Error, XMLNS}; + +pub struct Presence { + from: Option<JID>, + id: Option<String>, + to: Option<JID>, + 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>, +} + +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, + Subscribe, + Subscribed, + Unavailable, + Unsubscribe, + 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, + Dnd, + 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, +} + +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 +#[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) + } +} |