diff options
author | 2025-04-01 20:42:51 +0100 | |
---|---|---|
committer | 2025-04-01 20:42:51 +0100 | |
commit | f7b88569987026bcfec6132ca97b1d8122f44edd (patch) | |
tree | e4c56b14a053ec17e00854ea5f7872a0c735bbfb | |
parent | 132d932e6264774ad7d6eab9f52719848c5c91bb (diff) | |
download | luz-f7b88569987026bcfec6132ca97b1d8122f44edd.tar.gz luz-f7b88569987026bcfec6132ca97b1d8122f44edd.tar.bz2 luz-f7b88569987026bcfec6132ca97b1d8122f44edd.zip |
feat(stanza): xep-0059: result set management
-rw-r--r-- | stanza/Cargo.toml | 1 | ||||
-rw-r--r-- | stanza/src/lib.rs | 2 | ||||
-rw-r--r-- | stanza/src/xep_0030/info.rs | 19 | ||||
-rw-r--r-- | stanza/src/xep_0030/items.rs | 24 | ||||
-rw-r--r-- | stanza/src/xep_0059.rs | 216 |
5 files changed, 257 insertions, 5 deletions
diff --git a/stanza/Cargo.toml b/stanza/Cargo.toml index 0e6f6b5..ad3bd0b 100644 --- a/stanza/Cargo.toml +++ b/stanza/Cargo.toml @@ -13,6 +13,7 @@ chrono = { version = "0.4.40", optional = true } rfc_6121 = [] xep_0004 = [] xep_0030 = [] +xep_0059 = [] xep_0060 = ["xep_0004", "dep:chrono"] xep_0199 = [] xep_0203 = ["dep:chrono"] diff --git a/stanza/src/lib.rs b/stanza/src/lib.rs index f4c0899..0bae7f7 100644 --- a/stanza/src/lib.rs +++ b/stanza/src/lib.rs @@ -13,6 +13,8 @@ pub mod stream_error; pub mod xep_0004; #[cfg(feature = "xep_0030")] pub mod xep_0030; +#[cfg(feature = "xep_0059")] +pub mod xep_0059; #[cfg(feature = "xep_0060")] pub mod xep_0060; #[cfg(feature = "xep_0199")] diff --git a/stanza/src/xep_0030/info.rs b/stanza/src/xep_0030/info.rs index 589fd08..94cbabb 100644 --- a/stanza/src/xep_0030/info.rs +++ b/stanza/src/xep_0030/info.rs @@ -3,6 +3,9 @@ use peanuts::{ DeserializeError, Element, }; +#[cfg(feature = "xep_0059")] +use crate::xep_0059::Set; + pub const XMLNS: &str = "http://jabber.org/protocol/disco#info"; #[derive(Debug, Clone)] @@ -10,6 +13,8 @@ pub struct Query { pub node: Option<String>, pub features: Vec<Feature>, pub identities: Vec<Identity>, + #[cfg(feature = "xep_0059")] + pub set: Option<Set>, } impl FromElement for Query { @@ -22,20 +27,30 @@ impl FromElement for Query { let features = element.children()?; let identities = element.children()?; + #[cfg(feature = "xep_0059")] + let set = element.child_opt()?; + Ok(Self { node, features, identities, + #[cfg(feature = "xep_0059")] + set, }) } } impl IntoElement for Query { fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("query", Some(XMLNS)) + let builder = Element::builder("query", Some(XMLNS)) .push_attribute_opt("node", self.node.clone()) .push_children(self.features.clone()) - .push_children(self.identities.clone()) + .push_children(self.identities.clone()); + + #[cfg(feature = "xep_0059")] + let builder = builder.push_child_opt(self.set.clone()); + + builder } } diff --git a/stanza/src/xep_0030/items.rs b/stanza/src/xep_0030/items.rs index 7af2bbf..471f3e1 100644 --- a/stanza/src/xep_0030/items.rs +++ b/stanza/src/xep_0030/items.rs @@ -4,12 +4,17 @@ use peanuts::{ Element, }; +#[cfg(feature = "xep_0059")] +use crate::xep_0059::Set; + pub const XMLNS: &str = "http://jabber.org/protocol/disco#items"; #[derive(Debug, Clone)] pub struct Query { pub node: Option<String>, pub items: Vec<Item>, + #[cfg(feature = "xep_0059")] + pub set: Option<Set>, } impl FromElement for Query { @@ -21,15 +26,28 @@ impl FromElement for Query { let items = element.pop_children()?; - Ok(Self { node, items }) + #[cfg(feature = "xep_0059")] + let set = element.child_opt()?; + + Ok(Self { + node, + items, + #[cfg(feature = "xep_0059")] + set, + }) } } impl IntoElement for Query { fn builder(&self) -> peanuts::element::ElementBuilder { - Element::builder("query", Some(XMLNS)) + let builder = Element::builder("query", Some(XMLNS)) .push_attribute_opt("node", self.node.clone()) - .push_children(self.items.clone()) + .push_children(self.items.clone()); + + #[cfg(feature = "xep_0059")] + let builder = builder.push_child_opt(self.set.clone()); + + builder } } diff --git a/stanza/src/xep_0059.rs b/stanza/src/xep_0059.rs new file mode 100644 index 0000000..01dbc6c --- /dev/null +++ b/stanza/src/xep_0059.rs @@ -0,0 +1,216 @@ +use peanuts::{ + element::{FromElement, IntoElement}, + Element, +}; + +pub const XMLNS: &str = "http://jabber.org/protocol/rsm"; + +#[derive(Debug, Clone)] +pub struct Set { + after: Option<After>, + before: Option<Before>, + count: Option<Count>, + first: Option<First>, + index: Option<Index>, + last: Option<Last>, + max: Option<Max>, +} + +impl FromElement for Set { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("set")?; + element.check_namespace(XMLNS)?; + + let after = element.pop_child_opt()?; + let before = element.pop_child_opt()?; + let count = element.pop_child_opt()?; + let first = element.pop_child_opt()?; + let index = element.pop_child_opt()?; + let last = element.pop_child_opt()?; + let max = element.pop_child_opt()?; + + Ok(Self { + after, + before, + count, + first, + index, + last, + max, + }) + } +} + +impl IntoElement for Set { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("set", Some(XMLNS)) + .push_child_opt(self.after.clone()) + .push_child_opt(self.before.clone()) + .push_child_opt(self.count.clone()) + .push_child_opt(self.first.clone()) + .push_child_opt(self.index.clone()) + .push_child_opt(self.last.clone()) + .push_child_opt(self.max.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct After(pub String); + +impl FromElement for After { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("after")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for After { + fn builder(&self) -> peanuts::element::ElementBuilder { + // TODO: better way for push_text to work, empty string should be empty element no matter what + let builder = Element::builder("after", Some(XMLNS)); + + if self.0.is_empty() { + builder + } else { + builder.push_text(self.0.clone()) + } + } +} + +#[derive(Debug, Clone)] +pub struct Before(pub String); + +impl FromElement for Before { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("before")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Before { + fn builder(&self) -> peanuts::element::ElementBuilder { + // TODO: better way for push_text to work, empty string should be empty element no matter what + let builder = Element::builder("before", Some(XMLNS)); + + if self.0.is_empty() { + builder + } else { + builder.push_text(self.0.clone()) + } + } +} + +#[derive(Debug, Clone)] +pub struct Count(pub i32); + +impl FromElement for Count { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("count")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Count { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("count", Some(XMLNS)).push_text(self.0) + } +} + +#[derive(Debug, Clone)] +pub struct Index(pub i32); + +impl FromElement for Index { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("index")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Index { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("index", Some(XMLNS)).push_text(self.0) + } +} + +#[derive(Debug, Clone)] +pub struct Last(pub String); + +impl FromElement for Last { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("last")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Last { + fn builder(&self) -> peanuts::element::ElementBuilder { + // TODO: better way for push_text to work, empty string should be empty element no matter what + let builder = Element::builder("last", Some(XMLNS)); + + if self.0.is_empty() { + builder + } else { + builder.push_text(self.0.clone()) + } + } +} + +#[derive(Debug, Clone)] +pub struct Max(pub i32); + +impl FromElement for Max { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("max")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Max { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("max", Some(XMLNS)).push_text(self.0) + } +} + +#[derive(Debug, Clone)] +pub struct First { + index: Option<i32>, + first: String, +} + +impl FromElement for First { + fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("first")?; + element.check_namespace(XMLNS)?; + + let index = element.attribute_opt("index")?; + + let first = element.value_opt()?.unwrap_or_default(); + + Ok(Self { index, first }) + } +} + +impl IntoElement for First { + fn builder(&self) -> peanuts::element::ElementBuilder { + let builder = + Element::builder("first", Some(XMLNS)).push_attribute_opt("index", self.index); + + if self.first.is_empty() { + builder + } else { + builder.push_text(self.first.clone()) + } + } +} |