diff options
| author | 2025-03-31 18:07:21 +0100 | |
|---|---|---|
| committer | 2025-03-31 18:07:21 +0100 | |
| commit | b18c1cc2568dc116de5944d9829303ef8cc61b00 (patch) | |
| tree | 3156d0c1114ae2d45df0f7a34924de4240a883bb /stanza | |
| parent | 6d59d6690c3e5afd936f067b32e9998bc834728c (diff) | |
| download | luz-b18c1cc2568dc116de5944d9829303ef8cc61b00.tar.gz luz-b18c1cc2568dc116de5944d9829303ef8cc61b00.tar.bz2 luz-b18c1cc2568dc116de5944d9829303ef8cc61b00.zip | |
feat(stanza): xep-0004: data forms
Diffstat (limited to '')
| -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()) +    } +} | 
