aboutsummaryrefslogtreecommitdiffstats
path: root/src/stanza/client
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2024-12-03 03:51:26 +0000
committerLibravatar cel 🌸 <cel@bunny.garden>2024-12-03 03:51:26 +0000
commit7c2577d196c059ab6e2d5b0efe5e036bdad75be7 (patch)
tree7649b705f0af85a8b521d8fa849f9ed77e2c201c /src/stanza/client
parentbe198ca15bbaf633c1535db5bae7091520546aed (diff)
downloadluz-7c2577d196c059ab6e2d5b0efe5e036bdad75be7.tar.gz
luz-7c2577d196c059ab6e2d5b0efe5e036bdad75be7.tar.bz2
luz-7c2577d196c059ab6e2d5b0efe5e036bdad75be7.zip
implement remaining rfc6120 xml schemas
Diffstat (limited to 'src/stanza/client')
-rw-r--r--src/stanza/client/error.rs4
-rw-r--r--src/stanza/client/message.rs155
-rw-r--r--src/stanza/client/mod.rs42
-rw-r--r--src/stanza/client/presence.rs194
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)
+ }
+}