diff options
| author | 2024-12-03 03:51:26 +0000 | |
|---|---|---|
| committer | 2024-12-03 03:51:26 +0000 | |
| commit | 7c2577d196c059ab6e2d5b0efe5e036bdad75be7 (patch) | |
| tree | 7649b705f0af85a8b521d8fa849f9ed77e2c201c | |
| parent | be198ca15bbaf633c1535db5bae7091520546aed (diff) | |
| download | luz-7c2577d196c059ab6e2d5b0efe5e036bdad75be7.tar.gz luz-7c2577d196c059ab6e2d5b0efe5e036bdad75be7.tar.bz2 luz-7c2577d196c059ab6e2d5b0efe5e036bdad75be7.zip | |
implement remaining rfc6120 xml schemas
| -rw-r--r-- | src/jabber.rs | 13 | ||||
| -rw-r--r-- | src/lib.rs | 10 | ||||
| -rw-r--r-- | src/stanza/client/error.rs | 4 | ||||
| -rw-r--r-- | src/stanza/client/message.rs | 155 | ||||
| -rw-r--r-- | src/stanza/client/mod.rs | 42 | ||||
| -rw-r--r-- | src/stanza/client/presence.rs | 194 | ||||
| -rw-r--r-- | src/stanza/mod.rs | 5 | ||||
| -rw-r--r-- | src/stanza/stanza_error.rs (renamed from src/stanza/error.rs) | 0 | ||||
| -rw-r--r-- | src/stanza/stream.rs | 30 | ||||
| -rw-r--r-- | src/stanza/stream_error.rs | 137 | 
10 files changed, 562 insertions, 28 deletions
| diff --git a/src/jabber.rs b/src/jabber.rs index 96cd73a..d5cfe13 100644 --- a/src/jabber.rs +++ b/src/jabber.rs @@ -2,19 +2,16 @@ use std::str;  use std::sync::Arc;  use async_recursion::async_recursion; -use peanuts::element::{FromElement, IntoElement}; +use peanuts::element::IntoElement;  use peanuts::{Reader, Writer};  use rsasl::prelude::{Mechname, SASLClient, SASLConfig}; -use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter, ReadHalf, WriteHalf}; -use tokio::time::timeout; +use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};  use tokio_native_tls::native_tls::TlsConnector; -use tracing::{debug, info, instrument, trace}; -use trust_dns_resolver::proto::rr::domain::IntoLabel; +use tracing::{debug, instrument};  use crate::connection::{Tls, Unencrypted};  use crate::error::Error;  use crate::stanza::bind::{Bind, BindType, FullJidType, ResourceType}; -use crate::stanza::client::error::Error as ClientError;  use crate::stanza::client::iq::{Iq, IqType, Query};  use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse};  use crate::stanza::starttls::{Proceed, StartTls}; @@ -257,7 +254,7 @@ where          // server to client          // may or may not send a declaration -        let decl = self.reader.read_prolog().await?; +        let _decl = self.reader.read_prolog().await?;          // receive stream element and validate          let text = str::from_utf8(self.reader.buffer.data()).unwrap(); @@ -471,7 +468,7 @@ mod tests {      #[tokio::test]      async fn negotiate() { -        let jabber = Connection::connect_user("test@blos.sm", "slayed".to_string()) +        let _jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())              .await              .unwrap()              .ensure_tls() @@ -9,14 +9,20 @@ pub mod jid;  pub mod stanza;  pub use connection::Connection; +use connection::Tls;  pub use error::Error;  pub use jabber::Jabber;  pub use jid::JID;  pub type Result<T> = std::result::Result<T, Error>; -pub async fn login<J: TryInto<JID>, P: AsRef<str>>(jid: J, password: P) -> Result<Connection> { -    todo!() +pub async fn login<J: AsRef<str>, P: AsRef<str>>(jid: J, password: P) -> Result<Jabber<Tls>> { +    Ok(Connection::connect_user(jid, password.as_ref().to_string()) +        .await? +        .ensure_tls() +        .await? +        .negotiate() +        .await?)  }  #[cfg(test)] 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) +    } +} diff --git a/src/stanza/mod.rs b/src/stanza/mod.rs index 84e80ab..32716d3 100644 --- a/src/stanza/mod.rs +++ b/src/stanza/mod.rs @@ -2,11 +2,10 @@ use peanuts::declaration::VersionInfo;  pub mod bind;  pub mod client; -pub mod error;  pub mod sasl; +pub mod stanza_error;  pub mod starttls;  pub mod stream; +pub mod stream_error;  pub static XML_VERSION: VersionInfo = VersionInfo::One; - -pub use error::Error; diff --git a/src/stanza/error.rs b/src/stanza/stanza_error.rs index 99c1f15..99c1f15 100644 --- a/src/stanza/error.rs +++ b/src/stanza/stanza_error.rs diff --git a/src/stanza/stream.rs b/src/stanza/stream.rs index c49a2bc..4f3c435 100644 --- a/src/stanza/stream.rs +++ b/src/stanza/stream.rs @@ -6,11 +6,12 @@ use peanuts::{element::Name, Element};  use tracing::debug;  use crate::stanza::bind; -use crate::{Error, JID}; +use crate::JID; -use super::client;  use super::sasl::{self, Mechanisms};  use super::starttls::{self, StartTls}; +use super::stream_error::{Error as StreamError, Text}; +use super::{client, stream_error};  pub const XMLNS: &str = "http://etherx.jabber.org/streams"; @@ -162,3 +163,28 @@ impl FromElement for Feature {          }      }  } + +pub struct Error { +    error: StreamError, +    text: Option<Text>, +} + +impl FromElement for Error { +    fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> { +        element.check_name("error")?; +        element.check_namespace(XMLNS)?; + +        let error = element.pop_child_one()?; +        let text = element.pop_child_opt()?; + +        Ok(Error { error, text }) +    } +} + +impl IntoElement for Error { +    fn builder(&self) -> ElementBuilder { +        Element::builder("error", Some(XMLNS)) +            .push_child(self.error.clone()) +            .push_child_opt(self.text.clone()) +    } +} diff --git a/src/stanza/stream_error.rs b/src/stanza/stream_error.rs new file mode 100644 index 0000000..37db8a1 --- /dev/null +++ b/src/stanza/stream_error.rs @@ -0,0 +1,137 @@ +use peanuts::{ +    element::{FromElement, IntoElement}, +    DeserializeError, Element, XML_NS, +}; + +pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-streams"; + +#[derive(Clone)] +pub enum Error { +    BadFormat, +    BadNamespacePrefix, +    Conflict, +    ConnectionTimeout, +    HostGone, +    HostUnknown, +    ImproperAddressing, +    InternalServerError, +    InvalidFrom, +    InvalidId, +    InvalidNamespace, +    InvalidXml, +    NotAuthorized, +    NotWellFormed, +    PolicyViolation, +    RemoteConnectionFailed, +    Reset, +    ResourceConstraint, +    RestrictedXml, +    SeeOtherHost(Option<String>), +    SystemShutdown, +    UndefinedCondition, +    UnsupportedEncoding, +    UnsupportedStanzaType, +    UnsupportedVersion, +} + +impl FromElement for Error { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        let error; +        match element.identify() { +            (Some(XMLNS), "bad-format") => error = Error::BadFormat, +            (Some(XMLNS), "bad-namespace-prefix") => error = Error::BadNamespacePrefix, +            (Some(XMLNS), "conflict") => error = Error::Conflict, +            (Some(XMLNS), "connection-timeout") => error = Error::ConnectionTimeout, +            (Some(XMLNS), "host-gone") => error = Error::HostGone, +            (Some(XMLNS), "host-unknown") => error = Error::HostUnknown, +            (Some(XMLNS), "improper-addressing") => error = Error::ImproperAddressing, +            (Some(XMLNS), "internal-server-error") => error = Error::InternalServerError, +            (Some(XMLNS), "invalid-from") => error = Error::InvalidFrom, +            (Some(XMLNS), "invalid-id") => error = Error::InvalidId, +            (Some(XMLNS), "invalid-namespace") => error = Error::InvalidNamespace, +            (Some(XMLNS), "invalid-xml") => error = Error::InvalidXml, +            (Some(XMLNS), "not-authorized") => error = Error::NotAuthorized, +            (Some(XMLNS), "not-well-formed") => error = Error::NotWellFormed, +            (Some(XMLNS), "policy-violation") => error = Error::PolicyViolation, +            (Some(XMLNS), "remote-connection-failed") => error = Error::RemoteConnectionFailed, +            (Some(XMLNS), "reset") => error = Error::Reset, +            (Some(XMLNS), "resource-constraint") => error = Error::ResourceConstraint, +            (Some(XMLNS), "restricted-xml") => error = Error::RestrictedXml, +            (Some(XMLNS), "see-other-host") => { +                return Ok(Error::SeeOtherHost(element.pop_value_opt()?)) +            } +            (Some(XMLNS), "system-shutdown") => error = Error::SystemShutdown, +            (Some(XMLNS), "undefined-condition") => error = Error::UndefinedCondition, +            (Some(XMLNS), "unsupported-encoding") => error = Error::UnsupportedEncoding, +            (Some(XMLNS), "unsupported-stanza-type") => error = Error::UnsupportedStanzaType, +            (Some(XMLNS), "unsupported-version") => error = Error::UnsupportedVersion, +            _ => return Err(DeserializeError::UnexpectedElement(element)), +        } +        element.no_more_content()?; +        return Ok(error); +    } +} + +impl IntoElement for Error { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        match self { +            Error::BadFormat => Element::builder("bad-format", Some(XMLNS)), +            Error::BadNamespacePrefix => Element::builder("bad-namespace-prefix", Some(XMLNS)), +            Error::Conflict => Element::builder("conflict", Some(XMLNS)), +            Error::ConnectionTimeout => Element::builder("connection-timeout", Some(XMLNS)), +            Error::HostGone => Element::builder("host-gone", Some(XMLNS)), +            Error::HostUnknown => Element::builder("host-unknown", Some(XMLNS)), +            Error::ImproperAddressing => Element::builder("improper-addressing", Some(XMLNS)), +            Error::InternalServerError => Element::builder("internal-server-error", Some(XMLNS)), +            Error::InvalidFrom => Element::builder("invalid-from", Some(XMLNS)), +            Error::InvalidId => Element::builder("invalid-id", Some(XMLNS)), +            Error::InvalidNamespace => Element::builder("invalid-namespace", Some(XMLNS)), +            Error::InvalidXml => Element::builder("invalid-xml", Some(XMLNS)), +            Error::NotAuthorized => Element::builder("not-authorized", Some(XMLNS)), +            Error::NotWellFormed => Element::builder("not-well-formed", Some(XMLNS)), +            Error::PolicyViolation => Element::builder("policy-violation", Some(XMLNS)), +            Error::RemoteConnectionFailed => { +                Element::builder("remote-connection-failed", Some(XMLNS)) +            } +            Error::Reset => Element::builder("reset", Some(XMLNS)), +            Error::ResourceConstraint => Element::builder("resource-constraint", Some(XMLNS)), +            Error::RestrictedXml => Element::builder("restricted-xml", Some(XMLNS)), +            Error::SeeOtherHost(h) => { +                Element::builder("see-other-host", Some(XMLNS)).push_text_opt(h.clone()) +            } +            Error::SystemShutdown => Element::builder("system-shutdown", Some(XMLNS)), +            Error::UndefinedCondition => Element::builder("undefined-condition", Some(XMLNS)), +            Error::UnsupportedEncoding => Element::builder("unsupported-encoding", Some(XMLNS)), +            Error::UnsupportedStanzaType => { +                Element::builder("unsupported-stanza-type", Some(XMLNS)) +            } +            Error::UnsupportedVersion => Element::builder("unsupported-version", Some(XMLNS)), +        } +    } +} + +#[derive(Clone)] +pub struct Text { +    text: Option<String>, +    lang: Option<String>, +} + +impl FromElement for Text { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        element.check_name("text")?; +        element.check_name(XMLNS)?; + +        let lang = element.attribute_opt_namespaced("lang", XML_NS)?; +        let text = element.pop_value_opt()?; + +        Ok(Text { lang, text }) +    } +} + +impl IntoElement for Text { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        Element::builder("text", Some(XMLNS)) +            .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) +            .push_text_opt(self.text.clone()) +    } +} | 
