aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-03-24 20:51:15 +0000
committerLibravatar cel 🌸 <cel@bunny.garden>2025-03-24 20:51:15 +0000
commit85a3093674506b60b31a023ae40df1d65b2f1fb4 (patch)
tree3443a36a3bf02544440a934d387c5b85e83b2264
parentf0cb64670a7fdc49c334d99428458dfaf450171c (diff)
downloadluz-85a3093674506b60b31a023ae40df1d65b2f1fb4.tar.gz
luz-85a3093674506b60b31a023ae40df1d65b2f1fb4.tar.bz2
luz-85a3093674506b60b31a023ae40df1d65b2f1fb4.zip
feat(stanza): xep-0030
-rw-r--r--stanza/Cargo.toml1
-rw-r--r--stanza/src/client/iq.rs19
-rw-r--r--stanza/src/lib.rs2
-rw-r--r--stanza/src/xep_0030/info.rs110
-rw-r--r--stanza/src/xep_0030/items.rs63
-rw-r--r--stanza/src/xep_0030/mod.rs2
6 files changed, 197 insertions, 0 deletions
diff --git a/stanza/Cargo.toml b/stanza/Cargo.toml
index f21ac1e..1b6e4d0 100644
--- a/stanza/Cargo.toml
+++ b/stanza/Cargo.toml
@@ -11,3 +11,4 @@ chrono = { version = "0.4.40", optional = true }
[features]
xep_0203 = ["dep:chrono"]
+xep_0030 = []
diff --git a/stanza/src/client/iq.rs b/stanza/src/client/iq.rs
index 5c39938..5f5ccd2 100644
--- a/stanza/src/client/iq.rs
+++ b/stanza/src/client/iq.rs
@@ -13,6 +13,9 @@ use crate::{
xep_0199::{self, Ping},
};
+#[cfg(feature = "xep_0030")]
+use crate::xep_0030::{self, info, items};
+
use super::XMLNS;
#[derive(Debug, Clone)]
@@ -31,6 +34,10 @@ pub struct Iq {
#[derive(Clone, Debug)]
pub enum Query {
Bind(Bind),
+ #[cfg(feature = "xep_0030")]
+ DiscoInfo(info::Query),
+ #[cfg(feature = "xep_0030")]
+ DiscoItems(items::Query),
Ping(Ping),
Roster(roster::Query),
Unsupported,
@@ -44,6 +51,14 @@ impl FromElement for Query {
(Some(roster::XMLNS), "query") => {
Ok(Query::Roster(roster::Query::from_element(element)?))
}
+ #[cfg(feature = "xep_0030")]
+ (Some(xep_0030::info::XMLNS), "query") => {
+ Ok(Query::DiscoInfo(info::Query::from_element(element)?))
+ }
+ #[cfg(feature = "xep_0030")]
+ (Some(xep_0030::items::XMLNS), "query") => {
+ Ok(Query::DiscoItems(items::Query::from_element(element)?))
+ }
_ => Ok(Query::Unsupported),
}
}
@@ -57,6 +72,10 @@ impl IntoElement for Query {
Query::Roster(query) => query.builder(),
// TODO: consider what to do if attempt to serialize unsupported
Query::Unsupported => todo!(),
+ #[cfg(feature = "xep_0030")]
+ Query::DiscoInfo(query) => query.builder(),
+ #[cfg(feature = "xep_0030")]
+ Query::DiscoItems(query) => query.builder(),
}
}
}
diff --git a/stanza/src/lib.rs b/stanza/src/lib.rs
index 4629c07..c8daaa5 100644
--- a/stanza/src/lib.rs
+++ b/stanza/src/lib.rs
@@ -8,6 +8,8 @@ pub mod stanza_error;
pub mod starttls;
pub mod stream;
pub mod stream_error;
+#[cfg(feature = "xep_0030")]
+pub mod xep_0030;
pub mod xep_0199;
#[cfg(feature = "xep_0203")]
pub mod xep_0203;
diff --git a/stanza/src/xep_0030/info.rs b/stanza/src/xep_0030/info.rs
new file mode 100644
index 0000000..cec2dcb
--- /dev/null
+++ b/stanza/src/xep_0030/info.rs
@@ -0,0 +1,110 @@
+use peanuts::{
+ element::{FromElement, IntoElement},
+ DeserializeError, Element,
+};
+
+pub const XMLNS: &str = "http://jabber.org/protocol/disco#info";
+
+#[derive(Debug, Clone)]
+pub struct Query {
+ node: Option<String>,
+ features: Vec<Feature>,
+ identities: Vec<Identity>,
+}
+
+impl FromElement for Query {
+ fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("query")?;
+ element.check_namespace(XMLNS)?;
+
+ let node = element.attribute_opt("node")?;
+
+ let features = element.children()?;
+ let identities = element.children()?;
+
+ Ok(Self {
+ node,
+ features,
+ identities,
+ })
+ }
+}
+
+impl IntoElement for Query {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("query", Some(XMLNS))
+ .push_attribute_opt("node", self.node.clone())
+ .push_children(self.features.clone())
+ .push_children(self.identities.clone())
+ }
+}
+
+// no children
+#[derive(Debug, Clone)]
+pub struct Identity {
+ /// non empty string
+ pub category: String,
+ pub name: Option<String>,
+ /// non empty string
+ pub r#type: String,
+}
+
+impl FromElement for Identity {
+ fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("identity")?;
+ element.check_namespace(XMLNS)?;
+
+ let category: String = element.attribute("category")?;
+
+ if category.is_empty() {
+ return Err(DeserializeError::AttributeEmptyString(
+ "category".to_string(),
+ ));
+ }
+
+ let name = element.attribute_opt("name")?;
+ let r#type: String = element.attribute("type")?;
+
+ if r#type.is_empty() {
+ return Err(DeserializeError::AttributeEmptyString("type".to_string()));
+ }
+
+ Ok(Self {
+ category,
+ name,
+ r#type,
+ })
+ }
+}
+
+impl IntoElement for Identity {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("identity", Some(XMLNS))
+ .push_attribute("category", self.category.clone())
+ .push_attribute_opt("name", self.name.clone())
+ .push_attribute("type", self.r#type.clone())
+ }
+}
+
+// no children
+#[derive(Debug, Clone)]
+pub struct Feature {
+ pub var: String,
+}
+
+impl FromElement for Feature {
+ fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("feature")?;
+ element.check_namespace(XMLNS)?;
+
+ let var = element.attribute("var")?;
+
+ Ok(Self { var })
+ }
+}
+
+impl IntoElement for Feature {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("feature", Some(XMLNS)).push_attribute("var", self.var.clone())
+ }
+}
diff --git a/stanza/src/xep_0030/items.rs b/stanza/src/xep_0030/items.rs
new file mode 100644
index 0000000..78fe332
--- /dev/null
+++ b/stanza/src/xep_0030/items.rs
@@ -0,0 +1,63 @@
+use jid::JID;
+use peanuts::{
+ element::{FromElement, IntoElement},
+ Element,
+};
+
+pub const XMLNS: &str = "http://jabber.org/protocol/disco#items";
+
+#[derive(Debug, Clone)]
+pub struct Query {
+ node: Option<String>,
+ items: Vec<Item>,
+}
+
+impl FromElement for Query {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("query")?;
+ element.check_namespace(XMLNS)?;
+
+ let node = element.attribute_opt("node")?;
+
+ let items = element.pop_children()?;
+
+ Ok(Self { node, items })
+ }
+}
+
+impl IntoElement for Query {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("query", Some(XMLNS))
+ .push_attribute_opt("node", self.node.clone())
+ .push_children(self.items.clone())
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Item {
+ jid: JID,
+ name: Option<String>,
+ node: Option<String>,
+}
+
+impl FromElement for Item {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("item")?;
+ element.check_namespace(XMLNS)?;
+
+ let jid = element.attribute("jid")?;
+ let name = element.attribute_opt("name")?;
+ let node = element.attribute_opt("node")?;
+
+ Ok(Self { jid, name, node })
+ }
+}
+
+impl IntoElement for Item {
+ fn builder(&self) -> peanuts::element::ElementBuilder {
+ Element::builder("item", Some(XMLNS))
+ .push_attribute("jid", self.jid.clone())
+ .push_attribute_opt("name", self.name.clone())
+ .push_attribute_opt("node", self.node.clone())
+ }
+}
diff --git a/stanza/src/xep_0030/mod.rs b/stanza/src/xep_0030/mod.rs
new file mode 100644
index 0000000..914c17b
--- /dev/null
+++ b/stanza/src/xep_0030/mod.rs
@@ -0,0 +1,2 @@
+pub mod info;
+pub mod items;