diff options
author | 2025-03-31 18:07:21 +0100 | |
---|---|---|
committer | 2025-03-31 18:07:21 +0100 | |
commit | b18c1cc2568dc116de5944d9829303ef8cc61b00 (patch) | |
tree | 3156d0c1114ae2d45df0f7a34924de4240a883bb | |
parent | 6d59d6690c3e5afd936f067b32e9998bc834728c (diff) | |
download | luz-b18c1cc2568dc116de5944d9829303ef8cc61b00.tar.gz luz-b18c1cc2568dc116de5944d9829303ef8cc61b00.tar.bz2 luz-b18c1cc2568dc116de5944d9829303ef8cc61b00.zip |
feat(stanza): xep-0004: data forms
-rw-r--r-- | stanza/Cargo.toml | 1 | ||||
-rw-r--r-- | stanza/src/lib.rs | 2 | ||||
-rw-r--r-- | stanza/src/xep_0004.rs | 362 |
3 files changed, 365 insertions, 0 deletions
diff --git a/stanza/Cargo.toml b/stanza/Cargo.toml index e721a86..352d1ce 100644 --- a/stanza/Cargo.toml +++ b/stanza/Cargo.toml @@ -11,6 +11,7 @@ chrono = { version = "0.4.40", optional = true } [features] rfc_6121 = [] +xep_0004 = [] xep_0030 = [] xep_0199 = [] xep_0203 = ["dep:chrono"] diff --git a/stanza/src/lib.rs b/stanza/src/lib.rs index 9585353..85c5c84 100644 --- a/stanza/src/lib.rs +++ b/stanza/src/lib.rs @@ -9,6 +9,8 @@ pub mod stanza_error; pub mod starttls; pub mod stream; pub mod stream_error; +#[cfg(feature = "xep_0004")] +pub mod xep_0004; #[cfg(feature = "xep_0030")] pub mod xep_0030; #[cfg(feature = "xep_0199")] diff --git a/stanza/src/xep_0004.rs b/stanza/src/xep_0004.rs new file mode 100644 index 0000000..f929517 --- /dev/null +++ b/stanza/src/xep_0004.rs @@ -0,0 +1,362 @@ +use std::str::FromStr; + +use peanuts::{ + element::{FromElement, IntoElement}, + DeserializeError, Element, +}; + +pub const XMLNS: &str = "jabber:x:data"; + +#[derive(Debug, Clone)] +pub struct X { + pub r#type: XType, + pub instructions: Vec<Instructions>, + pub title: Option<Title>, + pub fields: Vec<Field>, + pub reported: Option<Reported>, + pub items: Vec<Item>, +} + +impl FromElement for X { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("x")?; + element.check_namespace(XMLNS)?; + + let r#type = element.attribute("type")?; + + let instructions = element.pop_children()?; + let title = element.pop_child_opt()?; + let fields = element.pop_children()?; + let reported = element.pop_child_opt()?; + let items = element.pop_children()?; + + Ok(Self { + r#type, + instructions, + title, + fields, + reported, + items, + }) + } +} + +impl IntoElement for X { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("x", Some(XMLNS)) + .push_attribute("type", self.r#type.clone()) + .push_children(self.instructions.clone()) + .push_child_opt(self.title.clone()) + .push_children(self.fields.clone()) + .push_child_opt(self.reported.clone()) + .push_children(self.items.clone()) + } +} + +#[derive(Debug, Clone)] +pub enum XType { + Cancel, + Form, + Result, + Submit, +} + +impl FromStr for XType { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "cancel" => Ok(Self::Cancel), + "form" => Ok(Self::Form), + "result" => Ok(Self::Result), + "submit" => Ok(Self::Submit), + s => Err(DeserializeError::FromStr(s.to_string())), + } + } +} + +impl ToString for XType { + fn to_string(&self) -> String { + match self { + XType::Cancel => "cancel", + XType::Form => "form", + XType::Result => "result", + XType::Submit => "submit", + } + .to_owned() + } +} + +#[derive(Debug, Clone)] +pub struct Instructions(pub String); + +impl FromElement for Instructions { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("instructions")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Instructions { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("instructions", Some(XMLNS)).push_text(self.0.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct Title(pub String); + +impl FromElement for Title { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("instructions")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Title { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("title", Some(XMLNS)).push_text(self.0.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct Field { + pub label: Option<String>, + pub r#type: Option<FieldType>, + pub var: Option<String>, + pub desc: Option<Desc>, + pub required: bool, + pub values: Vec<Value>, + pub options: Vec<XOption>, +} + +impl FromElement for Field { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("field")?; + element.check_namespace(XMLNS)?; + + let label = element.attribute_opt("label")?; + let r#type = element.attribute_opt("type")?; + let var = element.attribute_opt("var")?; + + let desc = element.pop_child_opt()?; + let required; + if let Some(_) = element.pop_child_opt::<Required>()? { + required = true; + } else { + required = false; + } + let values = element.pop_children()?; + let options = element.pop_children()?; + + Ok(Self { + label, + r#type, + var, + desc, + required, + values, + options, + }) + } +} + +impl IntoElement for Field { + fn builder(&self) -> peanuts::element::ElementBuilder { + let mut builder = Element::builder("field", Some(XMLNS)) + .push_attribute_opt("label", self.label.clone()) + .push_attribute_opt("type", self.r#type.clone()) + .push_attribute_opt("var", self.var.clone()) + .push_child_opt(self.desc.clone()); + + if self.required { + builder = builder.push_child(Required) + } + + builder + .push_children(self.values.clone()) + .push_children(self.options.clone()) + } +} + +#[derive(Debug, Clone)] +pub enum FieldType { + Boolean, + Fixed, + Hidden, + JIDMulti, + JIDSingle, + ListMulti, + ListSingle, + TextMulti, + TextPrivate, + TextSingle, +} + +impl FromStr for FieldType { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "boolean" => Ok(Self::Boolean), + "fixed" => Ok(Self::Fixed), + "hidden" => Ok(Self::Hidden), + "jid-multi" => Ok(Self::JIDMulti), + "jid-single" => Ok(Self::JIDSingle), + "list-multi" => Ok(Self::ListMulti), + "list-single" => Ok(Self::ListSingle), + "text-multi" => Ok(Self::TextMulti), + "text-private" => Ok(Self::TextPrivate), + "text-single" => Ok(Self::TextSingle), + s => Err(DeserializeError::FromStr(s.to_string())), + } + } +} + +impl ToString for FieldType { + fn to_string(&self) -> String { + match self { + FieldType::Boolean => "boolean", + FieldType::Fixed => "fixed", + FieldType::Hidden => "hidden", + FieldType::JIDMulti => "jid-multi", + FieldType::JIDSingle => "jid-single", + FieldType::ListMulti => "list-multi", + FieldType::ListSingle => "list-single", + FieldType::TextMulti => "text-multi", + FieldType::TextPrivate => "text-private", + FieldType::TextSingle => "text-single", + } + .to_owned() + } +} + +#[derive(Debug, Clone)] +pub struct Desc(pub String); + +impl FromElement for Desc { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("desc")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Desc { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("desc", Some(XMLNS)).push_text(self.0.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct Required; + +impl FromElement for Required { + fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("required")?; + element.check_namespace(XMLNS)?; + + Ok(Self) + } +} + +impl IntoElement for Required { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("required", Some(XMLNS)) + } +} + +#[derive(Debug, Clone)] +pub struct XOption { + pub label: Option<String>, + pub value: Value, +} + +impl FromElement for XOption { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("option")?; + element.check_namespace(XMLNS)?; + + let label = element.attribute_opt("label")?; + + let value = element.pop_child_one()?; + + Ok(Self { label, value }) + } +} + +impl IntoElement for XOption { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("option", Some(XMLNS)) + .push_attribute_opt("label", self.label.clone()) + .push_child(self.value.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct Value(pub String); + +impl FromElement for Value { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("value")?; + element.check_namespace(XMLNS)?; + + Ok(Self(element.pop_value_opt()?.unwrap_or_default())) + } +} + +impl IntoElement for Value { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("value", Some(XMLNS)).push_text(self.0.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct Reported { + /// fields SHOULD NOT contain value children + fields: Vec<Field>, +} + +impl FromElement for Reported { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("reported")?; + element.check_namespace(XMLNS)?; + + let fields = element.pop_children()?; + + Ok(Self { fields }) + } +} + +impl IntoElement for Reported { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("reported", Some(XMLNS)).push_children(self.fields.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct Item { + fields: Vec<Field>, +} + +impl FromElement for Item { + fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { + element.check_name("item")?; + element.check_namespace(XMLNS)?; + + let fields = element.pop_children()?; + + Ok(Self { fields }) + } +} + +impl IntoElement for Item { + fn builder(&self) -> peanuts::element::ElementBuilder { + Element::builder("item", Some(XMLNS)).push_children(self.fields.clone()) + } +} |